linux 构建ios_如何使用SlackTextViewController构建iOS聊天应用

linux 构建ios

by Neo Ighodaro

由新Ighodaro

如何使用SlackTextViewController构建iOS聊天应用 (How to build an iOS chat app with SlackTextViewController)

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:

这是正在运行的应用程序的屏幕录像:

要求 (Requirements)

To follow along with the tutorial, you will need the requirements listed below:

要继续学习本教程,您需要满足以下要求:

  • Xcode 7 or higher.

    Xcode 7或更高版本。

  • Knowledge of Swift and Xcode interface builder.

    熟悉Swift和Xcode接口构建器。
  • Cocoapods installed on your machine.

    您的机器上安装了可可足类 。

  • Node.js and NPM installed on your machine.

    您的计算机上安装了Node.js和NPM 。

  • Basic knowledge of JavaScript (Node.js and Express).

    JavaScript的基础知识(Node.js和Express)。
  • A Pusher Chatkit application. Create one here.

    Pusher Chatkit应用程序。 在此处创建一个。

Assuming you have all the requirements, let’s get started.

假设您具有所有要求,那就开始吧。

创建我们的Chatkit应用程序 (Creating our Chatkit Application)

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 LocatorKey 。 您将需要这些值来向Chatkit API发出请求。

That’s all! Now let’s create a backend that will help our application communicate with the Chatkit API.

就这样! 现在,让我们创建一个后端,以帮助我们的应用程序与Chatkit API进行通信。

为Pusher Chatkit创建Node.js后端 (Creating a Node.js backend for Pusher Chatkit)

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_LOCATORandPUSHER_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服务器运行。

创建我们的iOS应用程序 (Creating our iOS application)

Launch Xcode and create a “Single View App” project.

启动Xcode并创建一个“ Single View App”项目。

安装我们的Cocoapods软件包 (Installing our Cocoapods packages)

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 version 1.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。

配置我们的iOS应用程序 (Configuring our iOS application)

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结构中,我们定义了ENDPOINTINSTANCE_LOCATORENDPOINT是指向Node.js应用程序所在的远程Web服务器的URL。 INSTANCE_LOCATOR包含在Pusher Chatkit仪表板中为您的Chatkit应用程序提供的实例定位器。

Now let’s focus on creating the storyboard and other parts.

现在,让我们专注于创建情节提要和其他部分。

创建我们的应用程序的情节提要和控制器 (Creating our application’s storyboard and controllers)

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 @IBOutlets 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

测试我们的Pusher Chatkit应用程序 (Testing Our Pusher Chatkit Application)

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应用程序通信。 现在您可以运行您的应用程序了。

结论 (Conclusion)

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

你可能感兴趣的:(ios,java,python,vue,github,ViewUI)