
В настоящее время многие приложения предлагают своим пользователям функции чата и обмена сообщениями внутри приложения. Мессенджер внутри приложения может быть полезен для таких вещей, как чат поддержки в реальном времени или обмен сообщениями внутри приложения с другими пользователями приложения.
В этой статье мы собираемся изучить, как использовать Pusher Chatkit (который находится в стадии бета-тестирования на момент написания этой статьи) и SlackTextViewController для создания приложения чата.
💡 SlackTextViewController - подкласс UIViewController с расширяющимся представлением ввода текста и другими полезными функциями обмена сообщениями. Он предназначен для замены UITableViewController и UICollectionViewController.
Чтобы следовать этому руководству, необходимо базовое понимание Swift и Node.js.
Вот запись экрана нашего приложения в действии:

Требования
Чтобы следовать руководству, вам потребуются перечисленные ниже требования:
- Xcode 7 или выше.
- Знание построителя интерфейсов Swift и Xcode.
- Кокоаподы установлены на вашу машину.
- Node.js и NPM установлены на вашем компьютере.
- Базовые знания JavaScript (Node.js и Express).
- Приложение Pusher Chatkit. Создайте его здесь.
Предполагая, что у вас есть все требования, приступим.
Создание нашего приложения Chatkit
Перейдите на страницу Chatkit, создайте учетную запись и создайте приложение Chatkit из панели управления.

Следуйте инструкциям мастера «Приступая к работе» до конца, чтобы он помог вам создать новую учетную запись пользователя и новую комнату чата.

На этом же экране после завершения работы мастера «Приступить к работе» нажмите «Ключи», чтобы получить указатель экземпляра и ключ вашего приложения. Эти значения понадобятся вам для выполнения запросов к Chatkit API.
Это все! Теперь давайте создадим бэкэнд, который поможет нашему приложению взаимодействовать с Chatkit API.
Создание серверной части Node.js для Pusher Chatkit
Прежде чем мы создадим наше приложение для iOS, давайте создадим для него серверную часть Node.js. Приложение будет взаимодействовать с серверной частью, чтобы делать такие вещи, как получение токена, необходимого для выполнения запросов.
Откройте свой терминал и создайте там новый каталог, в котором будет размещаться веб-приложение. В этом веб-приложении мы определим некоторые маршруты, которые будут содержать логику для выполнения запросов к Chatkit API.
Выполните команду ниже, чтобы создать каталог, в котором будет находиться наше веб-приложение:
$ mkdir ChattrBackend
Создайте новый package.json файл в корне каталога и вставьте его содержимое ниже:
{
"main": "index.js",
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.3",
"pusher-chatkit-server": "^0.5.0"
}
}
Теперь откройте терминал и выполните команду ниже, чтобы начать установку зависимостей:
$ npm install
Когда установка будет завершена, создайте новый index.js файл и вставьте содержимое ниже:
// Pull in the libraries const express = require('express'); const bodyParser = require('body-parser'); const app = express(); const Chatkit = require('pusher-chatkit-server'); const chatkit = new Chatkit.default(require('./config.js'))// Express Middlewares app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false }));// -------------------------------------------------------- // Creates a new user using the Chatkit API // -------------------------------------------------------- app.post('/users', (req, res) => { let username = req.body.username; chatkit.createUser(username, username) .then(r => res.json({username})) .catch(e => res.json({error: e.error_description, type: e.error_type})) })// -------------------------------------------------------- // Generate a token and return it // -------------------------------------------------------- app.post('/auth', (req, res) => { let resp = chatkit.authenticate({grant_type: req.body.grant_type}, req.query.user_id) res.json(resp) });// -------------------------------------------------------- // Index // -------------------------------------------------------- app.get('/', (req, res) => { res.json("It works!"); });// -------------------------------------------------------- // Handle 404 errors // -------------------------------------------------------- app.use((req, res, next) => { let err = new Error('Not Found'); err.status = 404; next(err); });// -------------------------------------------------------- // Serve application // -------------------------------------------------------- app.listen(4000, function(){ console.log('App listening on port 4000!') });
В приведенном выше коде у нас есть образец Express-приложения. Приложение имеет два основных маршрута. Маршрут /users создает нового пользователя с помощью Chatkit API. Созданный пользователь может затем запросить токен, используя маршрут /auth. Токены используются для проверки личности пользователя, отправляющего запрос к Chatkit API.
Наконец, давайте создадим config.js файл в том же корневом каталоге. Здесь мы определим ключи Chatkit. Вставьте содержимое ниже в файл:
module.exports = {
instanceLocator: "PUSHER_CHATKIT_INSTANCE_LOCATOR",
key: "PUSHER_CHATKIT_KEY",
}
Не забудьте заменить *PUSHER_CHATKIT_*` INSTANCE_LOCATOR and PUSHER_CHATKIT_KEY `на фактические значения для вашего приложения Chatkit. Вы можете найти значения в разделе «Ключи» на панели инструментов Chatkit.
На этом мы закончили создание приложения Node.js. Выполните команду ниже, чтобы запустить приложение Node.js:
$ node index.js
💡 Вы можете оставить окно терминала открытым и запустить другое окно терминала, чтобы сервер Node.js оставался работающим.
Создание нашего приложения для iOS
Запустите Xcode и создайте проект «Приложение для одного просмотра».

Установка наших пакетов Cocoapods
Когда вы создали приложение, закройте Xcode и запустите новое окно терминала. cd в корень каталога вашего мобильного приложения. Выполните команду ниже, чтобы инициализировать Cocoapods в проекте:
$ pod init
Это создаст новый Podfile. В этом файле мы можем определить наши зависимости Cocoapods. Откройте файл и вставьте следующее:
platform :ios, '10.0'target 'Chattr' do use_frameworks!pod 'PusherChatkit', '~> 0.4.0' pod 'Alamofire', '~> 4.5.1' pod 'SlackTextViewController', git: 'https://github.com/slackhq/SlackTextViewController.git', branch: 'master' end
Теперь запустите pod install, чтобы установить зависимости.
⚠️
SlackTextViewControllerимеет ошибку в iOS 11, когда текстовое представление не реагирует на щелчки. Хотя это было исправлено в версии1.9.6, эта версия была недоступна для Cocoapods на момент написания этой статьи, поэтому нам пришлось вытащить мастер из подфайла.
Когда установка будет завершена, откройте новый .xcworkspace файл, созданный Cocoapods, в корне вашего проекта. Это запустит Xcode.
Настройка нашего приложения для iOS
В Xcode откройте файл AppDelegate.swift и замените содержимое файла следующим кодом:
import UIKitstruct AppConstants { static let ENDPOINT = "http://localhost:4000" static let INSTANCE_LOCATOR = "PUSHER_CHATKIT_INSTANCE_LOCATOR" }@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window?.backgroundColor = UIColor.white return true } }
В структуре AppConstants мы определили ENDPOINT и INSTANCE_LOCATOR. ENDPOINT - это URL-адрес удаленного веб-сервера, на котором находится ваше приложение Node.js. INSTANCE_LOCATOR содержит указатель экземпляров, предоставленный для вашего приложения Chatkit на панели управления Pusher Chatkit.
Теперь давайте сосредоточимся на создании раскадровки и других частей.
Создание раскадровки и контроллеров нашего приложения
Откройте файл Main.storyboard, и в нем мы создадим интерфейс приложения. В нашей раскадровке будет четыре сцены. Это будет выглядеть примерно так, как на скриншоте ниже:

В первой сцене View Controller давайте создадим LoginViewController и свяжем его со сценой View Controller в раскадровке. Создайте новый контроллер представления и вставьте приведенный ниже код:
import UIKit import Alamofireclass LoginViewController: UIViewController { var username: String! @IBOutlet weak var loginButton: UIButton! @IBOutlet weak var textField: UITextField! }extension LoginViewController { // MARK: Initialize override func viewDidLoad() { super.viewDidLoad()self.loginButton.isEnabled = falseself.loginButton.addTarget(self, action: #selector(loginButtonPressed), for: .touchUpInside) self.textField.addTarget(self, action: #selector(typingUsername), for: .editingChanged) }// MARK: Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) -> Void { if segue.identifier == "loginSegue" { let ctrl = segue.destination as! UINavigationController let actualCtrl = ctrl.viewControllers.first as! RoomListTableViewController actualCtrl.username = self.username } }// MARK: Helpers @objc func typingUsername(_ sender: UITextField) { self.loginButton.isEnabled = sender.text!.characters.count >= 3 }@objc func loginButtonPressed(_ sender: Any) { let payload: Parameters = ["username": self.textField.text!]self.loginButton.isEnabled = falseAlamofire.request(AppConstants.ENDPOINT + "/users", method: .post, parameters: payload).validate().responseJSON { (response) in switch response.result { case .success(_): self.username = self.textField.text! self.performSegue(withIdentifier: "loginSegue", sender: self) case .failure(let error): print(error) } } } }
В приведенном выше коде мы определили два @IBOutlet, которые мы подключим к сцене View Controller в раскадровке. В методе prepare мы готовимся к переходу к RoomListTableViewController, устанавливая свойство username в этом классе. В обработчике loginButtonPressed мы запускаем запрос к приложению Node.js, которое мы создали ранее, чтобы создать нового пользователя.
Откройте раскадровку и свяжите первую сцену с классом LoginViewController. Добавьте UIButton и UITextField в сцену контроллера представления. Теперь подключите UITextField к свойству textField как точку отсчета. Также подключите UIButton к свойству loginButton в качестве опорной точки.
Затем добавьте в раскадровку контроллер навигации. Создайте ручной переход между контроллером представления и контроллером навигации и установите идентификатор этого перехода на loginSegue.

Затем создайте новый контроллер с именем RoomListTableViewController. В файле Main.storyboard установите этот новый класс в качестве настраиваемого класса для TableViewController, присоединенного к контроллеру навигации. Теперь в классе RoomListTableViewController замените содержимое следующим кодом:
import UIKit import PusherChatkitclass RoomListTableViewController: UITableViewController { var username: String! var selectedRoom: PCRoom? var currentUser: PCCurrentUser! var availableRooms = [PCRoom]() var activityIndicator = UIActivityIndicatorView() }// MARK: - Initialize - extension RoomListTableViewController: PCChatManagerDelegate { override func viewDidLoad() -> Void { super.viewDidLoad() self.setNavigationItemTitle() self.initActivityIndicator() self.initPusherChatkit() }private func setNavigationItemTitle() -> Void { self.navigationItem.title = "Rooms" }private func initActivityIndicator() -> Void { self.activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray self.activityIndicator.center = self.view.center self.view.addSubview(self.activityIndicator) self.activityIndicator.startAnimating() }private func initPusherChatkit() -> Void { self.initPusherChatManager { [weak self] (currentUser) in guard let strongSelf = self else { return } strongSelf.currentUser = currentUser strongSelf.activityIndicator.stopAnimating() strongSelf.tableView.reloadData() } }private func initPusherChatManager(completion: @escaping (_ success: PCCurrentUser) -> Void) -> Void { let chatManager = ChatManager( instanceId: AppConstants.INSTANCE_LOCATOR, tokenProvider: PCTokenProvider(url: AppConstants.ENDPOINT + "/auth", userId: username) )chatManager.connect(delegate: self) { (user, error) in guard error == nil else { return } guard let user = user else { return }// Get a list of all rooms. Attempt to join the room. user.getAllRooms { rooms, error in guard error == nil else { return }self.availableRooms = rooms!rooms!.forEach { room in user.joinRoom(room) { room, error in guard error == nil else { return } } }DispatchQueue.main.async { self.tableView.reloadData() } }DispatchQueue.main.async { completion(user) } } } }// MARK: - UITableViewController Overrides - extension RoomListTableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.availableRooms.count }override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let roomTitle = self.availableRooms[indexPath.row].name let cell = tableView.dequeueReusableCell(withIdentifier: "RoomCell", for: indexPath) cell.textLabel?.text = "\(roomTitle)"return cell }override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.selectedRoom = self.availableRooms[indexPath.row] self.performSegue(withIdentifier: "segueToRoomViewController", sender: self) } }// MARK: - Navigation - extension RoomListTableViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) -> Void { if segue.identifier == "segueToRoomViewController" { let roomViewController = segue.destination as! RoomViewController roomViewController.room = self.selectedRoom roomViewController.currentUser = self.currentUser } } }
Этот класс предназначен для отображения всех доступных чатов, к которым пользователи могут подключаться и общаться в чате. Посмотрим, что делают некоторые части класса.
Первое расширение содержит инициализаторы. В методе viewDidLoad мы настраиваем заголовок контроллера, индикатор активности и Pusher Chatkit.
В initPusherChatManager мы инициализируем tokenProvider, который извлекает токен из нашей конечной точки Node.js. Затем мы создаем chatManager с помощью локатора экземпляров нашего приложения Chatkit и tokenProvider, и подключаемся к Chatkit.
Во втором расширении мы переопределяем некоторые методы контроллера табличного представления. Мы делаем это, чтобы отображать имена каналов в строках. В последнем методе второго расширения в строке 100 мы вызываем метод performSegue(withIdentifier: "segueToRoomViewController", sender: self), который будет перемещать страницу к новому контроллеру представления.
Последнее расширение имеет метод prepare. Это подготавливает View Controller, к которому мы переходим, прежде чем мы туда доберемся. Теперь давайте создадим контроллер представления и переход, необходимый для доступа к нему.
Для нашей последней сцены раскадровки создайте класс RoomViewController. В файле Main.storyboard перетащите последний контроллер представления на доску.
Установите для настраиваемого класса нового контроллера представления значение RoomViewController. Кроме того, создайте ручной переход от контроллера табличного представления к нему и назовите переход segueToRoomViewController:

Откройте класс RoomViewController и замените его содержимое следующим:
import UIKit import PusherChatkit import SlackTextViewControllerclass RoomViewController: SLKTextViewController, PCRoomDelegate { var room: PCRoom! var messages = [Message]() var currentUser: PCCurrentUser! override var tableView: UITableView { get { return super.tableView! } } }// MARK: - Initialize - extension RoomViewController { override func viewDidLoad() { super.viewDidLoad() self.subscribeToRoom() self.setNavigationItemTitle() self.configureSlackTableViewController() }private func subscribeToRoom() -> Void { self.currentUser.subscribeToRoom(room: self.room, roomDelegate: self) }private func setNavigationItemTitle() -> Void { self.navigationItem.title = self.room.name }private func configureSlackTableViewController() -> Void { self.bounces = true self.isInverted = true self.shakeToClearEnabled = true self.isKeyboardPanningEnabled = true self.textInputbar.maxCharCount = 256 self.tableView.separatorStyle = .none self.textInputbar.counterStyle = .split self.textInputbar.counterPosition = .top self.textInputbar.autoHideRightButton = true self.textView.placeholder = "Enter Message..."; self.shouldScrollToBottomAfterKeyboardShows = false self.textInputbar.editorTitle.textColor = UIColor.darkGray self.textInputbar.editorRightButton.tintColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1)self.tableView.register(MessageCell.classForCoder(), forCellReuseIdentifier: MessageCell.MessengerCellIdentifier) self.autoCompletionView.register(MessageCell.classForCoder(), forCellReuseIdentifier: MessageCell.AutoCompletionCellIdentifier) } }// MARK: - UITableViewController Overrides - extension RoomViewController { override class func tableViewStyle(for decoder: NSCoder) -> UITableViewStyle { return .plain }override func numberOfSections(in tableView: UITableView) -> Int { return 1 }override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if tableView == self.tableView { return self.messages.count }return 0 }override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return self.messageCellForRowAtIndexPath(indexPath) }override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if tableView == self.tableView { let message = self.messages[(indexPath as NSIndexPath).row]if message.text.characters.count == 0 { return 0 }let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineBreakMode = .byWordWrapping paragraphStyle.alignment = .leftlet pointSize = MessageCell.defaultFontSize()let attributes = [ NSAttributedStringKey.font: UIFont.systemFont(ofSize: pointSize), NSAttributedStringKey.paragraphStyle: paragraphStyle ]var width = tableView.frame.width - MessageCell.kMessageTableViewCellAvatarHeight width -= 25.0let titleBounds = (message.username as NSString!).boundingRect( with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: attributes, context: nil )let bodyBounds = (message.text as NSString!).boundingRect( with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: attributes, context: nil )var height = titleBounds.height height += bodyBounds.height height += 40if height < MessageCell.kMessageTableViewCellMinimumHeight { height = MessageCell.kMessageTableViewCellMinimumHeight }return height }return MessageCell.kMessageTableViewCellMinimumHeight } }// MARK: - Overrides - extension RoomViewController { override func keyForTextCaching() -> String? { return Bundle.main.bundleIdentifier }override func didPressRightButton(_ sender: Any!) { self.textView.refreshFirstResponder() self.sendMessage(textView.text) super.didPressRightButton(sender) } }// MARK: - Delegate Methods - extension RoomViewController { public func newMessage(message: PCMessage) { let msg = self.PCMessageToMessage(message) let indexPath = IndexPath(row: 0, section: 0) let rowAnimation: UITableViewRowAnimation = self.isInverted ? .bottom : .top let scrollPosition: UITableViewScrollPosition = self.isInverted ? .bottom : .topDispatchQueue.main.async { self.tableView.beginUpdates() self.messages.insert(msg, at: 0) self.tableView.insertRows(at: [indexPath], with: rowAnimation) self.tableView.endUpdates()self.tableView.scrollToRow(at: indexPath, at: scrollPosition, animated: true) self.tableView.reloadRows(at: [indexPath], with: .automatic) self.tableView.reloadData() } } }// MARK: - Helpers - extension RoomViewController { private func PCMessageToMessage(_ message: PCMessage) -> Message { return Message(id: message.id, username: message.sender.displayName, text: message.text) }private func sendMessage(_ message: String) -> Void { guard let room = self.room else { return } self.currentUser?.addMessage(text: message, to: room, completionHandler: { (messsage, error) in guard error == nil else { return } }) }private func messageCellForRowAtIndexPath(_ indexPath: IndexPath) -> MessageCell { let cell = self.tableView.dequeueReusableCell(withIdentifier: MessageCell.MessengerCellIdentifier) as! MessageCell let message = self.messages[(indexPath as NSIndexPath).row]cell.bodyLabel().text = message.text cell.titleLabel().text = message.usernamecell.usedForMessage = true cell.indexPath = indexPath cell.transform = self.tableView.transformreturn cell } }
Приведенный выше класс расширяет SlackTableViewController, что автоматически дает ему доступ к некоторым интересным функциям этого контроллера. В приведенном выше коде мы разбили класс на 5 расширений. Давайте возьмем каждое расширение и немного объясним, что в них происходит.
В первом расширении мы подписываемся на комнату, устанавливаем имя комнаты в качестве заголовка страницы и настраиваем SlackTableViewController. В методе configureSlackTableViewController мы просто настраиваем части нашего SlackTableViewController.
Во втором расширении мы переопределяем методы контроллера табличного представления. Мы также устанавливаем количество разделов, количество строк и устанавливаем Message, который будет отображаться в каждой строке. И, наконец, мы также вычисляем динамическую высоту каждой ячейки в зависимости от символов в сообщении.
В третьем расширении у нас есть функция didPressRightButton, которая вызывается каждый раз, когда пользователь нажимает кнопку «Отправить» для отправки сообщения.
В четвертом расширении у нас есть функции, доступные из PCRoomDelegate. В функции newMessage мы отправляем сообщение в Chatkit, а затем перезагружаем таблицу для отображения вновь добавленных данных.
В пятом и последнем расширении мы определяем функции, которые должны быть помощниками. Метод PCMessageToMessage преобразует PCMessage в нашу собственную Message структуру (мы определим это позже). Метод sendMessage отправляет сообщение в Chatkit API. Наконец, у нас есть метод messageCellForRowAtIndexPath. Этот метод просто прикрепляет сообщение к определенной строке с помощью indexPath.
Теперь создайте новый класс с именем MessageCell. Это будет класс View, в котором мы будем настраивать все, как будет выглядеть отдельная ячейка чата. В файле замените содержимое следующим:
import UIKit import SlackTextViewControllerstruct Message { var id: Int! var username: String! var text: String! }class MessageCell: UITableViewCell {static let kMessageTableViewCellMinimumHeight: CGFloat = 50.0; static let kMessageTableViewCellAvatarHeight: CGFloat = 30.0;static let MessengerCellIdentifier: String = "MessengerCell"; static let AutoCompletionCellIdentifier: String = "AutoCompletionCell";var _titleLabel: UILabel? var _bodyLabel: UILabel? var _thumbnailView: UIImageView?var indexPath: IndexPath? var usedForMessage: Bool?required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier)self.selectionStyle = .none self.backgroundColor = UIColor.whiteconfigureSubviews() }func configureSubviews() { contentView.addSubview(thumbnailView()) contentView.addSubview(titleLabel()) contentView.addSubview(bodyLabel())let views: [String:Any] = [ "thumbnailView": thumbnailView(), "titleLabel": titleLabel(), "bodyLabel": bodyLabel() ]let metrics = [ "tumbSize": MessageCell.kMessageTableViewCellAvatarHeight, "padding": 15, "right": 10, "left": 5 ]contentView.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:|-left-[thumbnailView(tumbSize)]-right-[titleLabel(>=0)]-right-|", options: [], metrics: metrics, views: views ))contentView.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:|-left-[thumbnailView(tumbSize)]-right-[bodyLabel(>=0)]-right-|", options: [], metrics: metrics, views: views ))contentView.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:|-right-[thumbnailView(tumbSize)]-(>=0)-|", options: [], metrics: metrics, views: views ))if (reuseIdentifier == MessageCell.MessengerCellIdentifier) { contentView.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:|-right-[titleLabel(20)]-left-[bodyLabel(>=0@999)]-left-|", options: [], metrics: metrics, views: views )) } else { contentView.addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:|[titleLabel]|", options: [], metrics: metrics, views: views )) } }// MARK: Gettersoverride func prepareForReuse() { super.prepareForReuse()selectionStyle = .nonelet pointSize: CGFloat = MessageCell.defaultFontSize()titleLabel().font = UIFont.boldSystemFont(ofSize: pointSize) bodyLabel().font = UIFont.systemFont(ofSize: pointSize) titleLabel().text = "" bodyLabel().text = "" }func titleLabel() -> UILabel { if _titleLabel == nil { _titleLabel = UILabel() _titleLabel?.translatesAutoresizingMaskIntoConstraints = false _titleLabel?.backgroundColor = UIColor.clear _titleLabel?.isUserInteractionEnabled = false _titleLabel?.numberOfLines = 0 _titleLabel?.textColor = UIColor.gray _titleLabel?.font = UIFont.boldSystemFont(ofSize: MessageCell.defaultFontSize()) }return _titleLabel! }func bodyLabel() -> UILabel { if _bodyLabel == nil { _bodyLabel = UILabel() _bodyLabel?.translatesAutoresizingMaskIntoConstraints = false _bodyLabel?.backgroundColor = UIColor.clear _bodyLabel?.isUserInteractionEnabled = false _bodyLabel?.numberOfLines = 0 _bodyLabel?.textColor = UIColor.darkGray _bodyLabel?.font = UIFont.systemFont(ofSize: MessageCell.defaultFontSize()) }return _bodyLabel! }func thumbnailView() -> UIImageView { if _thumbnailView == nil { _thumbnailView = UIImageView() _thumbnailView?.translatesAutoresizingMaskIntoConstraints = false _thumbnailView?.isUserInteractionEnabled = false _thumbnailView?.backgroundColor = UIColor(white: 0.9, alpha: 1.0) _thumbnailView?.layer.cornerRadius = MessageCell.kMessageTableViewCellAvatarHeight / 2.0 _thumbnailView?.layer.masksToBounds = true }return _thumbnailView! }class func defaultFontSize() -> CGFloat { var pointSize: CGFloat = 16.0let contentSizeCategory: String = String(describing: UIApplication.shared.preferredContentSizeCategory) pointSize += SLKPointSizeDifferenceForCategory(contentSizeCategory)return pointSize } }
В приведенном выше коде мы создаем класс, расширяющий UITableViewCell. Этот класс будет использоваться SlackTextViewController как класс для каждой строки сообщения. Он был зарегистрирован в RoomsViewController при настройке SlackTextViewController.
Тестирование нашего приложения Pusher Chatkit
Чтобы ваше приложение iOS подключалось к локальному серверу Node.js, вам необходимо внести некоторые изменения. В файле info.plist добавьте ключи, как показано ниже:

С этим изменением вы можете создавать и запускать свое приложение, и оно будет напрямую взаимодействовать с вашим локальным веб-приложением. Теперь вы можете запустить свое приложение.

Заключение
В этом руководстве мы смогли создать простое приложение для чата, используя SlackTextViewController и мощь Pusher Chatkit SDK. Надеюсь, вы кое-что узнали о том, как интегрировать Pusher Chatkit в существующие технологии и как он может улучшить обмен сообщениями в вашем приложении.
Этот пост впервые был опубликован в Pusher.