linux 构建ios
by Neo Ighodaro
由新Ighodaro
Nowadays, many applications offer in-app chat and messenger features to their users. The in-app messenger can be useful for things like live support chatting or in-app messaging with other application users.
如今,许多应用程序为其用户提供了应用程序内聊天和Messenger功能。 应用内Messenger可以用于诸如实时支持聊天或与其他应用用户进行应用内消息传递之类的事情。
In this article, we are going to explore how to use Pusher Chatkit (which is in beta at the time of writing this article) and SlackTextViewController
to create a chat application.
在本文中,我们将探讨如何使用Pusher Chatkit (在撰写本文时为beta版)和SlackTextViewController
来创建聊天应用程序。
? SlackTextViewController
is a drop-in UIViewController subclass with a growing text input view and other useful messaging features. It is meant to be a replacement for UITableViewController and UICollectionViewController.
? lackTextViewController
是一个UIViewController子类,具有不断增长的文本输入视图和其他有用的消息传递功能。 它旨在替代UITableViewController和UICollectionViewController。
A basic understanding of Swift and Node.js is needed to follow this tutorial.
要学习本教程,需要对Swift和Node.js有基本的了解。
Here is a screen recording of our application in action:
这是正在运行的应用程序的屏幕录像:
To follow along with the tutorial, you will need the requirements listed below:
要继续学习本教程,您需要满足以下要求:
Xcode 7 or higher.
Xcode 7或更高版本。
Cocoapods installed on your machine.
您的机器上安装了可可足类 。
Node.js and NPM installed on your machine.
您的计算机上安装了Node.js和NPM 。
A Pusher Chatkit application. Create one here.
Pusher Chatkit应用程序。 在此处创建一个。
Assuming you have all the requirements, let’s get started.
假设您具有所有要求,那就开始吧。
Go to the Chatkit page, create an account, and create a Chatkit application from the dashboard.
转到Chatkit页面,创建一个帐户,然后从仪表板创建一个Chatkit应用程序。
Follow the “Getting Started” wizard until the end so it helps you create a new user account and a new chat room.
按照“入门”向导进行操作,直到结束为止,这样可以帮助您创建新的用户帐户和新的聊天室。
In that same screen, after you have completed the “Get Started” wizard, click on “Keys” to get your application’s Instance Locator and Key. You will need these values to make requests to the Chatkit API.
在同一屏幕上,完成“入门”向导后,单击“密钥”以获取应用程序的Instance Locator和Key 。 您将需要这些值来向Chatkit API发出请求。
That’s all! Now let’s create a backend that will help our application communicate with the Chatkit API.
就这样! 现在,让我们创建一个后端,以帮助我们的应用程序与Chatkit API进行通信。
Before we create our iOS application, let’s create a Node.js backend for the application. The application will talk to the backend to do things like retrieve the token required to make requests.
在创建iOS应用程序之前,让我们为该应用程序创建一个Node.js后端。 该应用程序将与后端对话以执行诸如检索发出请求所需的令牌之类的事情。
Open your terminal, and in there create a new directory where the web application will reside. In this web application, we will define some routes that will contain logic for making requests to the Chatkit API.
打开您的终端,并在其中创建Web应用程序将驻留的新目录。 在此Web应用程序中,我们将定义一些路由,这些路由包含用于向Chatkit API发出请求的逻辑。
Run the command below to create the directory that will contain our web application:
运行以下命令以创建将包含我们的Web应用程序的目录:
$ mkdir ChattrBackend
Create a new package.json
file in the root of the directory and paste the contents below:
在目录的根目录中创建一个新的package.json
文件,并粘贴以下内容:
{ "main": "index.js", "dependencies": { "body-parser": "^1.17.2", "express": "^4.15.3", "pusher-chatkit-server": "^0.5.0" }}
Now open terminal and run the command below to start installing the dependencies:
现在打开终端并运行以下命令以开始安装依赖项:
$ npm install
When the installation is complete, create a new index.js
file and paste the content below:
安装完成后,创建一个新的index.js
文件并粘贴以下内容:
// Pull in the librariesconst 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 Middlewaresapp.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!')});
In the code above, we have a sample Express application. The application has two main routes. The /users
route creates a new user using the Chatkit API. The created user can then request a token using the /auth
route. Tokens are used to validate the identity of a user making a request to the Chatkit API.
在上面的代码中,我们有一个示例Express应用程序。 该应用程序有两条主要路线。 /users
路由使用Chatkit API创建一个新用户。 然后,创建的用户可以使用/auth
路由请求令牌。 令牌用于验证向Chatkit API发出请求的用户的身份。
Finally, let’s create a config.js
file in the same root directory. This is where we will define the Chatkit keys. Paste the contents below into the file:
最后,让我们在同一根目录中创建一个config.js
文件。 这是我们定义Chatkit键的地方。 将以下内容粘贴到文件中:
module.exports = { instanceLocator: "PUSHER_CHATKIT_INSTANCE_LOCATOR", key: "PUSHER_CHATKIT_KEY",}
Remember to replace *PUSHER_CHATKIT_*`
INSTANCE_LOCATORand
PUSHER_CHATKIT_KEY` with the actual values for your Chatkit application. You can find the values in the “Keys” section of the Chatkit dashboard.
请记住将*PUSHER_CHATKIT_*`
INSTANCE_LOCATOR and
PUSHER_CHATKIT_KEY`替换为您的Chatkit应用程序的实际值。 您可以在Chatkit仪表板的“键”部分中找到值。
Now we are done creating the Node.js application. Run the command below to start the Node.js application:
现在,我们完成了创建Node.js应用程序的工作。 运行以下命令以启动Node.js应用程序:
$ node index.js
? You may want to keep the terminal window open and launch another terminal window to keep the Node.js server running.
? 您可能需要保持终端窗口打开并启动另一个终端窗口以保持Node.js服务器运行。
Launch Xcode and create a “Single View App” project.
启动Xcode并创建一个“ Single View App”项目。
When you have created the application, close Xcode and launch a new terminal window. cd
to the root of the directory of your mobile application. Run the command below to initialize Cocoapods on the project:
创建应用程序后,关闭Xcode并启动一个新的终端窗口。 cd
到您的移动应用程序目录的根目录。 运行以下命令以在项目上初始化Cocoapods:
$ pod init
This will create a new Podfile
. In this file, we can define our Cocoapods dependencies. Open the file and paste the following:
这将创建一个新的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
Now run pod install
to install the dependencies.
现在运行pod install
安装依赖项。
⚠️
SlackTextViewController
has a bug in iOS 11 where the text view does not respond to clicks. Although it’s been fixed in version1.9.6
, that version was not available to Cocoapods at the time of writing this article, so we had to pull the master in the Podfile.⚠️
SlackTextViewController
在iOS的11有一个错误的文本视图不会响应点击 。 尽管此版本已在1.9.6
版中修复,但在撰写本文时Cocoapods尚无法使用该版本,因此我们不得不将主文件放入Podfile中。
When the installation is complete, open the new .xcworkspace
file that was generated by Cocoapods in the root of your project. This will launch Xcode.
安装完成后,在项目的根目录中打开由Cocoapods生成的新.xcworkspace
文件。 这将启动Xcode。
In Xcode, open the AppDelegate.swift
file and replace the contents of the file with the following code:
在Xcode中,打开AppDelegate.swift
文件,并将文件内容替换为以下代码:
import UIKit
struct AppConstants { static let ENDPOINT = "http://localhost:4000" static let INSTANCE_LOCATOR = "PUSHER_CHATKIT_INSTANCE_LOCATOR"}
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window?.backgroundColor = UIColor.white return true }}
In the AppConstants
struct we have defined the ENDPOINT
and INSTANCE_LOCATOR
. The ENDPOINT
is the URL to the remote web server where your Node.js application resides. The INSTANCE_LOCATOR
contains the instance locator provided for your Chatkit application in the Pusher Chatkit dashboard.
在AppConstants
结构中,我们定义了ENDPOINT
和INSTANCE_LOCATOR
。 ENDPOINT
是指向Node.js应用程序所在的远程Web服务器的URL。 INSTANCE_LOCATOR
包含在Pusher Chatkit仪表板中为您的Chatkit应用程序提供的实例定位器。
Now let’s focus on creating the storyboard and other parts.
现在,让我们专注于创建情节提要和其他部分。
Open the Main.storyboard
file and, in there, we will create the application’s interface. We will have four scenes on our storyboard. These will look something like the screenshot below:
打开Main.storyboard
文件,然后在其中创建应用程序的界面。 我们的故事板上将有四个场景。 这些看起来类似于以下屏幕截图:
In the first View Controller scene, let’s create a LoginViewController
and link it to the View Controller scene in the storyboard. Create the new View Controller and paste in the code below:
在第一个View Controller场景中,让我们创建一个LoginViewController
并将其链接到情节LoginViewController
中的View Controller场景。 创建新的View Controller并粘贴以下代码:
import UIKitimport 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) } } }}
In the code above, we have defined two @IBOutlet
s that we will connect to the View Controller scene in the storyboard. In the prepare
method, we prepare for the navigation to the RoomListTableViewController
by setting the username
property in that class. In the loginButtonPressed
handler, we fire a request to the Node.js application we created earlier to create the new user.
在上面的代码中,我们定义了两个@IBOutlet
,它们将连接到情节@IBOutlet
中的View Controller场景。 在prepare
方法中,我们通过在该类中设置username
属性来准备导航到RoomListTableViewController
。 在loginButtonPressed
处理程序中,我们向先前创建的Node.js应用程序发出请求以创建新用户。
Open the storyboard and link the first scene to the LoginViewController
class. Add a UIButton
and a UITextField
to the view controller scene. Now connect the UITextField
to the textField
property as a referencing outlet. Also connect the UIButton
to the loginButton
property as a referencing outlet.
打开情节提要,并将第一个场景链接到LoginViewController
类。 将一个UIButton
和一个UITextField
添加到视图控制器场景。 现在,将UITextField
连接到textField
属性作为参考出口。 loginButton
UIButton
连接到loginButton
属性作为参考插座。
Next, add a Navigation Controller to the storyboard. Create a manual segue between the View Controller and the Navigation Controller and set the ID of this segue to loginSegue
.
接下来,将导航控制器添加到情节提要。 在View Controller和Navigation Controller之间创建一个手动segue,并将此segue的ID设置为loginSegue
。
Next, create a new controller called RoomListTableViewController
. In the Main.storyboard
file, set this new class as the custom class for the TableViewController attached to the Navigation Controller. Now in the RoomListTableViewController
class, replace the contents with the following code:
接下来,创建一个名为RoomListTableViewController
的新控制器。 在Main.storyboard
文件中,将此新类设置为附加到导航控制器的TableViewController的自定义类。 现在在RoomListTableViewController
类中,将内容替换为以下代码:
import UIKitimport 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 } }}
This class is meant to show all the available chat rooms that users can connect to and chat on. Let’s see what some parts of the class do.
此类旨在显示用户可以连接并聊天的所有可用聊天室。 让我们看一下课程的某些部分。
The first extension contains the initializers. In the viewDidLoad
method, we set up the controller title, activity indicator, and Pusher Chatkit.
第一个扩展包含初始化程序。 在viewDidLoad
方法中,我们设置了控制器标题,活动指示器和Pusher Chatkit。
In the initPusherChatManager
, we initialize a tokenProvider
which fetches a token from our Node.js endpoint. We then create a chatManager
with our Chatkit application’s instance locator and the tokenProvider,
and connect to Chatkit.
在initPusherChatManager
,我们初始化了一个tokenProvider
,它从Node.js端点获取一个令牌。 然后,我们使用Chatkit应用程序的实例定位器和tokenProvider,
创建一个chatManager
tokenProvider,
并连接到Chatkit。
In the second extension, we override some table view controller methods. We do this so we can display the channel names in rows. In the last method of the second extension on line 100
, we call the method performSegue(withIdentifier: "segueToRoomViewController", sender: self)
which will navigate the page to a new View Controller.
在第二个扩展中,我们重写了一些表视图控制器方法。 我们这样做是为了在行中显示频道名称。 在第100
行第二个扩展的最后一个方法中,我们调用方法performSegue(withIdentifier: "segueToRoomViewController", sender: self)
,该方法将页面导航到新的View Controller。
The last extension has the prepare
method. This prepares the View Controller we are navigating to before we get there. Now, let’s create the View Controller and the segue needed to access it.
最后一个扩展具有prepare
方法。 这将为我们准备要导航的View Controller做好准备。 现在,让我们创建视图控制器和访问它所需的序列。
For our last storyboard scene, create a RoomViewController
class. In the Main.storyboard
file, drag in a final View Controller to the board.
对于最后一个故事板场景,创建一个RoomViewController
类。 在Main.storyboard
文件中,将最终的View Controller拖到板上。
Set the custom class of the new view controller to RoomViewController
. Also, create a manual segue from the table view controller to it and name the segue segueToRoomViewController
:
将新视图控制器的自定义类设置为RoomViewController
。 另外,从表视图控制器为其创建手动segue并将其命名为segue segueToRoomViewController
:
Open the RoomViewController
class and replace the contents with the following:
打开RoomViewController
类,并将内容替换为以下内容:
import UIKitimport PusherChatkitimport 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 } }
The class above extends SlackTableViewController
which automatically gives it access to some cool features of that controller. In the code above, we broke the class into 5 extensions. Let’s take each extension and explain a little about what is going on in them.
上面的类扩展了SlackTableViewController
,它自动使它可以访问该控制器的一些很酷的功能。 在上面的代码中,我们将类分为5个扩展。 让我们进行每个扩展,并解释其中发生的事情。
In the first extension, we subscribe to the room, set the room name as the page title, and configure the SlackTableViewController
. In the configureSlackTableViewController
method, we simply customise parts of our SlackTableViewController
.
在第一个扩展中,我们订阅房间,将房间名称设置为页面标题,并配置SlackTableViewController
。 在configureSlackTableViewController
方法中,我们只是自定义SlackTableViewController
各个部分。
In the second extension, we override the table view controller methods. We also set the number of sections, the number of rows, and set the Message
to be shown on each row. And finally we also calculate the dynamic height of each cell, depending on the characters the message has.
在第二个扩展中,我们重写了表视图控制器方法。 我们还设置了节数,行数,并设置了要在每行显示的Message
。 最后,我们还根据消息具有的字符来计算每个单元的动态高度。
In the third extension, we have the didPressRightButton
function which is called anytime the user presses send to send a message.
在第三扩展中,我们具有didPressRightButton
函数,该函数在用户按下send发送消息时被调用。
In the fourth extension, we have the functions available from the PCRoomDelegate
. In the newMessage
function we send the message to Chatkit and then we reload the table to display the newly added data.
在第四扩展中,我们具有PCRoomDelegate
可用的功能。 在newMessage
函数中,我们将消息发送到Chatkit,然后重新加载表以显示新添加的数据。
In the fifth and final extension, we define functions that are meant to be helpers. The PCMessageToMessage
method converts a PCMessage
to our own Message
struct (we will define this later). The sendMessage
method sends the message to the Chatkit API. Finally, we have the messageCellForRowAtIndexPath
method. This method simply gets the message attached to a particular row using the indexPath
.
在第五个也是最后一个扩展中,我们定义了旨在成为助手的功能。 该PCMessageToMessage
方法会将PCMessage
我们自己的Message
结构(我们将在稍后定义这个)。 sendMessage
方法将消息发送到Chatkit API。 最后,我们有messageCellForRowAtIndexPath
方法。 此方法仅使用indexPath
将消息附加到特定行。
Now create a new class called MessageCell
. This will be the View class where we will customise everything about how a single chat cell will look. In the file, replace the contents with the one below:
现在创建一个名为MessageCell
的新类。 这将是View类,我们将自定义有关单个聊天单元外观的所有内容。 在文件中,将内容替换为以下内容:
import UIKitimport 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 }}
In the code above, we create a class that extends UITableViewCell
. This class is going to be used by SlackTextViewController
as the class for each message row. It was registered in the RoomsViewController
when configuring the SlackTextViewController
.
在上面的代码中,我们创建一个扩展UITableViewCell
的类。 此类将由SlackTextViewController
用作每个消息行的类。 它是注册在RoomsViewController
配置时SlackTextViewController
。
To instruct your iOS app connect to your local Node.js server, you will need to make some changes. In the info.plist
file, add the keys as seen below:
要指示您的iOS应用连接到本地Node.js服务器,您需要进行一些更改。 在info.plist
文件中,添加密钥,如下所示:
With this change, you can build and run your application and it will talk directly with your local web application. Now you can run your application.
进行此更改后,您可以构建和运行您的应用程序,它将直接与本地Web应用程序通信。 现在您可以运行您的应用程序了。
In this tutorial, we were able to create a simple chat application using SlackTextViewController
and the power of the Pusher Chatkit SDK. Hopefully, you have learned a thing or two on how you can integrate Pusher Chatkit into existing technologies and how it can power messaging in your application.
在本教程中,我们能够使用SlackTextViewController
和Pusher Chatkit SDK的功能创建一个简单的聊天应用程序。 希望您对如何将Pusher Chatkit集成到现有技术中以及如何为应用程序中的消息提供动力方面学到了一两件事。
This post was first published to Pusher.
该帖子最初发布给Pusher 。
翻译自: https://www.freecodecamp.org/news/how-to-build-an-ios-chat-app-with-slacktextviewcontroller-e3d3291a46a2/
linux 构建ios