
Содержание статьи
автор Нео Игадаро

Сегодня текстовые редакторы приобретают все большую популярность, независимо от того, встраиваются они в форму комментариев на веб-сайте или используются в качестве блокнота. Есть много разных редакторов по выбору. В этой публикации мы не только узнаем, как создать красивый текстовый редактор для мобильных устройств на iOS, но и сделать возможным совместную работу над заметкой в реальном времени с помощью Pusher.
Обратите внимание, однако, что для простоты программы эта статья не будет рассматривать одновременные редактирования. Поэтому только один человек может редактировать одновременно, пока другие смотрят.
Программа будет работать, инициируя событие, когда вводится некоторый текст. Это событие будет отправлено в Pusher, а затем зафиксировано устройством соавтора и обновится автоматически.
Чтобы продолжить этот урок, вам понадобится следующее:
- Cocoapods: установить, запустить
gem install cocoapods
на вашей машине - Xcode
- А Приложение Pusher: Вы можете создать бесплатную учетную запись и приложение здесь
- Некоторые знания о Свифт язык
- Node.js
Наконец, для выполнения этого руководства необходимо базовое понимание Swift и Node.js.
Начало работы с нашим приложением iOS в Xcode
Запустите Xcode и создайте новый проект. Я позвоню своим Collabo. После выполнения инструкций мастера настройки и открытой рабочей области закройте Xcode, а затем cd
к корню вашего проекта и запустите команду pod init
. Это должно породить a Podfile
для вас. Измените содержимое Podfile
:
# Uncomment the next line to define a global platform for your project platform :ios, '9.0'
target 'textcollabo' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks!
# Pods for anonchat pod 'Alamofire' pod 'PusherSwift' end
Теперь запустите команду pod install
поэтому менеджер пакетов Cocoapods может привлечь необходимые зависимости. Когда это будет завершено, закройте Xcode (если открыт) и откройте файл .xcworkspace
файл, находящийся в корне папки вашего проекта.
Разработка представлений для нашего приложения iOS
Мы собираемся создать несколько представлений для нашей программы iOS. Это будет хребет, к которому мы подключим всю логику. Используя сюжетную доску Xcode, сделайте ваши взгляды похожими на скриншоты ниже.
Это LaunchScreen.storyboard файл. Я только что разработал что-то простое без функциональности.

Следующая раскадровка, которую мы разработаем, это Main.storyboard. Как известно из названия, он будет основным. Здесь мы имеем все важные взгляды, связанные с определенной логикой.

Здесь мы имеем три взгляда.
Первый вид создан так, чтобы выглядеть так же, как экран запуска, за исключением кнопки, которую мы связали, чтобы открыть второй вид.
Второй вид – это контроллер навигации. Он прилагается к третьему виду, который является a ViewController
. Мы установили третье представление в качестве корневого контроллера для нашего контроллера навигации.
В третьем взгляде мы имеем a UITextView
который можно редактировать, который размещается в представлении. Также есть метка, которая должна являться счетчиком символов. Это место, где мы будем увеличивать символы, когда пользователь вводит текст в текстовом представлении.
Кодирование приложения для общего текстового редактора iOS
Теперь, когда мы успешно создали представления, необходимые для загрузки программы, следующее, что мы сделаем, начнем кодировать логику программы.
Создайте новый файл класса какао и назовите его TextEditorViewController
и свяжите его с третьим представлением в Main.storyboard
файл. The TextViewController
следует также принять UITextViewDelegate
. Теперь ты можешь ctrl+drag
в UITextView
а также ctrl+drag
в UILabel
в Main.storyboard
файл к TextEditorViewController
класс.
Кроме того, вам следует импортировать PusherSwift
и AlamoFire
библиотеки в TextViewController
. После того как вы закончите, у вас должно быть что-то близко к этому:
import UIKit import PusherSwift import Alamofire
class TextEditorViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var textView: UITextView! @IBOutlet weak var charactersLabel: UILabel! }
Теперь нам нужно добавить некоторые свойства, которые нам понадобятся позже в контроллере.
import UIKit import PusherSwift import Alamofire
class TextEditorViewController: UIViewController, UITextViewDelegate { static let API_ENDPOINT = "
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var charactersLabel: UILabel!
var pusher : Pusher!
var chillPill = true
var placeHolderText = "Start typing..."
var randomUuid : String = "" }
Теперь мы разделим логику на три части:
- Просмотр и клавиатура событий
- Методы UITextViewDelegate
- Обработка событий Pusher.
Просмотр и клавиатура событий
Откройте TextEditorViewController
и обновите его с помощью следующих методов:
override func viewDidLoad() { super.viewDidLoad() // Notification trigger NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil) // Gesture recognizer view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedAwayFunction(_:)))) // Set the controller as the textView delegate textView.delegate = self // Set the device ID randomUuid = UIDevice.current.identifierForVendor!.uuidString // Listen for changes from Pusher listenForChanges() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if self.textView.text == "" { self.textView.text = placeHolderText self.textView.textColor = UIColor.lightGray } } func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { if self.charactersLabel.frame.origin.y == 1.0 { self.charactersLabel.frame.origin.y -= keyboardSize.height } } } func keyboardWillHide(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { if self.view.frame.origin.y != 1.0 { self.charactersLabel.frame.origin.y += keyboardSize.height } } }
В viewDidLoad
метод мы зарегистрировали функции клавиатуры, чтобы они реагировали на события клавиатуры. Мы также добавили средства распознавания жестов, которые будут закрывать клавиатуру, когда вы прикасаетесь за пределами UITextView
. И мы установили textView
делегировать самому контролеру. Наконец, мы вызвали функцию прослушивания новых обновлений (мы создадим ее позже).
В viewWillAppear
методом, мы просто сломали UITextView
чтобы иметь текст-заполнитель, поскольку по умолчанию файл UITextView
нет этой функции. Интересно почему, Apple…
В keyboardWillShow
и keyboardWillHide
функции мы сделали так, чтобы отметка подсчета символов поднималась вместе с клавиатурой и опускалась вместе с ней соответственно. Это не позволит клавиатуре закрыть этикетку, когда она активна.
Методы UITextViewDelegate
Обновите TextEditorViewController
со следующим:
func textViewDidChange(_ textView: UITextView) { charactersLabel.text = String(format: "%i Characters", textView.text.characters.count)
if textView.text.characters.count >= 2 { sendToPusher(text: textView.text) } }
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { self.textView.textColor = UIColor.black
if self.textView.text == placeHolderText { self.textView.text = "" }
return true }
func textViewDidEndEditing(_ textView: UITextView) { if textView.text == "" { self.textView.text = placeHolderText self.textView.textColor = UIColor.lightGray } }
func tappedAwayFunction(_ sender: UITapGestureRecognizer) { textView.resignFirstResponder() }
The textViewDidChange
метод просто обновляет метку количества символов, а также посылает изменения в Pusher с помощью нашего серверного API (который мы создадим в минуту).
The textViewShouldBeginEditing
получено из UITextViewDelegate
и она запускается, когда текстовое представление собирается редактировать. Здесь мы в основном играем с заполнителем, так же, как и textViewDidEndEditing
метод.
Наконец, в tappedAwayFunction
мы определяем обратный вызов события жеста, который мы зарегистрировали в предыдущем разделе. В методе мы в основном отбрасываем клавиатуру.
Обработка событий Pusher
Обновите контроллер с помощью следующих методов:
func sendToPusher(text: String) { let params: Parameters = ["text": text, "from": randomUuid]
Alamofire.request(TextEditorViewController.API_ENDPOINT + "/update_text", method: .post, parameters: params).validate().responseJSON { response in switch response.result {
case .success: print("Succeeded") case .failure(let error): print(error) } } }
func listenForChanges() { pusher = Pusher(key: "PUSHER_KEY", options: PusherClientOptions( host: .cluster("PUSHER_CLUSTER") ))
let channel = pusher.subscribe("collabo") let _ = channel.bind(eventName: "text_update", callback: { (data: Any?) -> Void in
if let data = data as? [String: AnyObject] { let fromDeviceId = data["deviceId"] as! String
if fromDeviceId != self.randomUuid { let text = data["text"] as! String self.textView.text = text self.charactersLabel.text = String(format: "%i Characters", text.characters.count) } } })
pusher.connect() }
В sendToPusher
метода, мы присылаем полезную нагрузку в нашу серверную программу с помощью AlamoFire
который, в свою очередь, пришлет его в Pusher.
В listenForChanges
Затем мы прослушиваем изменения в тексте и, если таковые имеются, применяем изменения в текстовом представлении.
? Рemember заменить ключ и кластер на фактическое значение, которое вы получили из панели инструментов Pusher.
Если вы внимательно придерживались учебника, то ваш TextEditorViewController
должно выглядеть примерно так:
import UIKit import PusherSwift import Alamofire
class TextEditorViewController: UIViewController, UITextViewDelegate { static let API_ENDPOINT = "
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var charactersLabel: UILabel!
var pusher : Pusher!
var chillPill = true
var placeHolderText = "Start typing..."
var randomUuid : String = ""
override func viewDidLoad() { super.viewDidLoad()
// Notification trigger NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
// Gesture recognizer view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedAwayFunction(_:))))
// Set the controller as the textView delegate textView.delegate = self
// Set the device ID randomUuid = UIDevice.current.identifierForVendor!.uuidString
// Listen for changes from Pusher listenForChanges() }
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated)
if self.textView.text == "" { self.textView.text = placeHolderText self.textView.textColor = UIColor.lightGray } }
func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { if self.charactersLabel.frame.origin.y == 1.0 { self.charactersLabel.frame.origin.y -= keyboardSize.height } } }
func keyboardWillHide(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { if self.view.frame.origin.y != 1.0 { self.charactersLabel.frame.origin.y += keyboardSize.height } } }
func textViewDidChange(_ textView: UITextView) { charactersLabel.text = String(format: "%i Characters", textView.text.characters.count)
if textView.text.characters.count >= 2 { sendToPusher(text: textView.text) } }
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { self.textView.textColor = UIColor.black
if self.textView.text == placeHolderText { self.textView.text = "" }
return true }
func textViewDidEndEditing(_ textView: UITextView) { if textView.text == "" { self.textView.text = placeHolderText self.textView.textColor = UIColor.lightGray } }
func tappedAwayFunction(_ sender: UITapGestureRecognizer) { textView.resignFirstResponder() }
func sendToPusher(text: String) { let params: Parameters = ["text": text, "from": randomUuid]
Alamofire.request(TextEditorViewController.API_ENDPOINT + "/update_text", method: .post, parameters: params).validate().responseJSON { response in switch response.result {
case .success: print("Succeeded") case .failure(let error): print(error) } } }
func listenForChanges() { pusher = Pusher(key: "PUSHER_KEY", options: PusherClientOptions( host: .cluster("PUSHER_CLUSTER") ))
let channel = pusher.subscribe("collabo") let _ = channel.bind(eventName: "text_update", callback: { (data: Any?) -> Void in
if let data = data as? [String: AnyObject] { let fromDeviceId = data["deviceId"] as! String
if fromDeviceId != self.randomUuid { let text = data["text"] as! String self.textView.text = text self.charactersLabel.text = String(format: "%i Characters", text.characters.count) } } })
pusher.connect() } }
Прекрасно! Теперь нам нужно сделать бэкенд программы.
Создание серверной программы Node
Теперь, когда мы закончили работу с частью Swift, мы можем сосредоточиться на создании бэкенда Node.js для программы. Мы будем использовать Express, чтобы быстро что-нибудь запустить.
Создайте каталог для веб-приложения и создайте несколько новых файлов.
The index.js файл:
let path = require('path'); let Pusher = require('pusher'); let express = require('express'); let bodyParser = require('body-parser'); let app = express(); let pusher = new Pusher(require('./config.js'));
app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false }));
app.post('/update_text', function(req, res){ var payload = {text: req.body.text, deviceId: req.body.from} pusher.trigger('collabo', 'text_update', payload) res.json({success: 200}) });
app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); });
module.exports = app;
app.listen(4000, function(){ console.log('App listening on port 4000!'); });
В вышеприведенном файле JS мы используем Express для создания простой программы. В /update_text
маршрута, мы просто получаем полезную нагрузку и передаем его Pusher. Ничего сложного там нет.
Создать package.json файл также:
{ "main": "index.js", "dependencies": { "body-parser": "^1.17.2", "express": "^4.15.3", "path": "^0.12.7", "pusher": "^1.5.1" } }
The package.json файл, где мы определяем все зависимости NPM.
Последний созданный файл a config.js файл. Вот где мы определим значение конфигурации для нашей программы Pusher:
module.exports = { appId: 'PUSHER_ID', key: 'PUSHER_KEY', secret: 'PUSHER_SECRET', cluster: 'PUSHER_CLUSTER', encrypted: true };
? Рemember заменить ключ и кластер на фактическое значение, которое вы получили из панели инструментов Pusher.
Теперь беги npm install
в каталоге, а затем node index.js
после завершения установки npm. Вы должны увидеть Приложение прослушивает порт 4000! сообщения.

Тестирование программы
После того, как у вас запущен веб-сервер локального узла, вам нужно будет внести некоторые изменения, чтобы ваше приложение могло общаться с локальным веб-сервером. В info.plist
файл, внесите следующие изменения:

Благодаря этому изменению вы можете создать и запустить свою программу, и она будет общаться непосредственно с вашим локальным веб-приложением.
Вывод
В этой статье мы рассказали, как создать текстовый редактор для совместной работы в реальном времени на iOS с помощью Pusher. Надеемся, вы узнали кое-что, следуя учебнику. Для практики можно расширить статусы, чтобы поддерживать больше экземпляров.
Эта публикация была впервые опубликована в Pusher.