В настоящее время многие приложения предлагают своим пользователям функции чата и обмена сообщениями внутри приложения. Мессенджер внутри приложения может быть полезен для таких вещей, как чат поддержки в реальном времени или обмен сообщениями внутри приложения с другими пользователями приложения.
В этой статье мы собираемся изучить, как использовать 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 UIKit
struct 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 Alamofire
class 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 = false
self.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 = false
Alamofire.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 PusherChatkit
class 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 SlackTextViewController
class 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 = .left
let pointSize = MessageCell.defaultFontSize()
let attributes = [ NSAttributedStringKey.font: UIFont.systemFont(ofSize: pointSize), NSAttributedStringKey.paragraphStyle: paragraphStyle ]
var width = tableView.frame.width - MessageCell.kMessageTableViewCellAvatarHeight width -= 25.0
let 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 += 40
if 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 : .top
DispatchQueue.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.username
cell.usedForMessage = true cell.indexPath = indexPath cell.transform = self.tableView.transform
return cell } }
Приведенный выше класс расширяет SlackTableViewController
, что автоматически дает ему доступ к некоторым интересным функциям этого контроллера. В приведенном выше коде мы разбили класс на 5 расширений. Давайте возьмем каждое расширение и немного объясним, что в них происходит.
В первом расширении мы подписываемся на комнату, устанавливаем имя комнаты в качестве заголовка страницы и настраиваем SlackTableViewController
. В методе configureSlackTableViewController
мы просто настраиваем части нашего SlackTableViewController
.
Во втором расширении мы переопределяем методы контроллера табличного представления. Мы также устанавливаем количество разделов, количество строк и устанавливаем Message
, который будет отображаться в каждой строке. И, наконец, мы также вычисляем динамическую высоту каждой ячейки в зависимости от символов в сообщении.
В третьем расширении у нас есть функция didPressRightButton
, которая вызывается каждый раз, когда пользователь нажимает кнопку «Отправить» для отправки сообщения.
В четвертом расширении у нас есть функции, доступные из PCRoomDelegate
. В функции newMessage
мы отправляем сообщение в Chatkit, а затем перезагружаем таблицу для отображения вновь добавленных данных.
В пятом и последнем расширении мы определяем функции, которые должны быть помощниками. Метод PCMessageToMessage
преобразует PCMessage
в нашу собственную Message
структуру (мы определим это позже). Метод sendMessage
отправляет сообщение в Chatkit API. Наконец, у нас есть метод messageCellForRowAtIndexPath
. Этот метод просто прикрепляет сообщение к определенной строке с помощью indexPath
.
Теперь создайте новый класс с именем MessageCell
. Это будет класс View, в котором мы будем настраивать все, как будет выглядеть отдельная ячейка чата. В файле замените содержимое следующим:
import UIKit import SlackTextViewController
struct 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.white
configureSubviews() }
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: Getters
override func prepareForReuse() { super.prepareForReuse()
selectionStyle = .none
let 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.0
let 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.