你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 用 JSQMessagesViewController 創建一個 iOS 聊天 App - 第 3 部分

用 JSQMessagesViewController 創建一個 iOS 聊天 App - 第 3 部分

編輯:IOS開發綜合

在本教程中,我將介紹如何創建一個簡單的 iOS 聊天 App(用 swift 和 Syncano)。

在第一部分和第二部分,我們創建了一個新項目,用 JSQMessagesViewController 作為前端,用 Syncano 作為 App 的後端。

在第三部分,我們將增加用戶登錄認證——包括注冊、登錄和顯示每條消息的發送者名稱。

如果你錯過了前面的部分,你可以在這裡找到它們:

Create an iOS Chat App using JSQMessagesViewController – Part 1 Create an iOS Chat App using JSQMessagesViewController – Part 21

你可以從第一部分開始,也可以從這裡下載第二部分的代碼然後開始。

注意,這個項目使用了 CocoaPods,如果你沒有用過它,不知道如何安裝它——請參考第一部分。

導入 IQKeyboardManagerSwift

添加一個 CocoaPod

讓我們來添加一個新的 CocoaPod —— 它叫做 IQKeyboardManagerSwift。因為我們會創建自己的登錄/注冊界面——這會用到 TextField 組件——我們不想將精力分散到和鍵盤的交互處理上,所以我們要用到這個庫。

打開終端,進入我們的項目路徑,例如:

cd ~/path/to/my/project/SyncanoChat

打開 Podfile:

open Podfile

加入這一句:

pod ‘JSQMessagesViewController’

修改後的 Podfile 最終變成這樣:

# Uncomment this line to define a global platform for your project
# platform :ios, '8.0'
# Uncomment this line if you're using Swift
use_frameworks!

target 'SyncanoChat' do
    pod 'syncano-ios'
    pod 'JSQMessagesViewController'
    pod 'IQKeyboardManagerSwift'
end

target 'SyncanoChatTests' do

end

target 'SyncanoChatUITests' do

end

保存文件,關閉文本編輯器,輸入終端命令:

pod update

當命令執行完,打開 Workspace 文件:

open SyncanoChat.xcworkspace

編譯項目,確認沒有任何錯誤發生。

使用 IQKeyboardManagerSwift

Open AppDelegate.swift file and add two new imports - one for IQKeyboardManagerSwift and one for Syncano. Beginning of your file should contain these lines:

打開 AppDelegate.swift 導入兩個庫 —— 一個是 IQKeyboardManagerSwift,一個是 Syncano:

import syncano_ios
import IQKeyboardManagerSwift

在 application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool 方法中,啟用 keyboard manager,創建一個 Syncano 共享實例:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        Syncano.sharedInstanceWithApiKey(syncanoApiKey, instanceName: syncanoInstanceName)
        IQKeyboardManager.sharedManager().enable = true
        IQKeyboardManager.sharedManager().shouldResignOnTouchOutside = true
        return true
    }

這時,Xcode 會報告 use of unresolved identifiers 錯誤。我們馬上就來解決這個問題。

添加 settings 文件

為了偷懶,我們會使用一個新文件,用於保存 Syncano 的登錄憑證。

新建一個 Swift 文件,命名為 Settings。

打開 Settings.swift,編輯內容為:

let syncanoApiKey = ""
let syncanoInstanceName = ""
let syncanoChannelName = "messages"

在這裡輸入你的 API key 和實例名 (也就是你寫在 ViewController.swift 的那兩個值).

打開 ViewController.swift 刪除這兩句:

```swift
let syncanoChannelName = "messages"
let syncano = Syncano.sharedInstanceWithApiKey("YOUR_API_KEY", instanceName: "YOUR_INSTANCE_NAME")





保存文件,編譯項目,確保它能正常運行。

新建 LoginViewController

要實現登錄和注冊邏輯,我們需要增加一個 Controller。新建一個 Swift 文件,命名為 LoginViewController。

編輯其內容為:

import UIKit
import syncano_ios

let loginViewControllerIdentifier = "LoginViewController"

protocol LoginDelegate {
    func didSignUp()
    func didLogin()
}

class LoginViewController: UIViewController {
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!

    var delegate : LoginDelegate?
}

//MARK - UI
extension LoginViewController {
    @IBAction func loginPressed(sender: UIButton) {
        if self.areBothUsernameAndPasswordFilled() {
            self.login(self.getUsername()!, password: self.getPassword()!, finished: { error in
                if (error != nil) {
                    //handle error
                    print("Login, error: \(error)")
                } else {
                    self.cleanTextFields()
                    self.delegate?.didLogin()
                }

            })
        }
    }

    @IBAction func signUpPressed(sender: UIButton) {
        if self.areBothUsernameAndPasswordFilled() {
            self.signUp(self.getUsername()!, password: self.getPassword()!, finished: { error in
                if (error != nil) {
                    //handle error
                    print("Sign Up, error: \(error)")
                } else {
                    self.cleanTextFields()
                    self.delegate?.didSignUp()
                }
            })
        }
    }

    func getUsername() -> String? {
        return self.emailTextField.text
    }

    func getPassword() -> String? {
        return self.passwordTextField.text
    }

    func areBothUsernameAndPasswordFilled() -> Bool {
        if let username = self.emailTextField.text, password = self.passwordTextField.text {
            if (username.characters.count > 0 && password.characters.count > 0) {
                return true
            }
        }
        return false
    }

    func cleanTextFields() {
        self.emailTextField.text = nil
        self.passwordTextField.text = nil
    }
}

//MARK - Syncano
extension LoginViewController {
    func login(username: String, password: String, finished: (NSError!) -> ()) {
        SCUser.loginWithUsername(username, password: password) { error in
            finished(error)
        }
    }

    func signUp(username: String, password: String, finished: (NSError!) -> ()) {
        SCUser.registerWithUsername(username, password: password) { error in
            finished(error)
        }
    }
}





這段代碼都做了些什麼?

LoginDelegate 協議:定義了兩個方法。根據我們的例子,委托對象是 ViewController,所以一旦用戶登錄或者創建了新賬號,就通過這兩個方法讓我們的主控制器知道。 @IBAction func loginPressed(sender: UIButton) 和 @IBAction func signUpPressed(sender: UIButton) 方法:處理相應的按鈕事件。在這兩個方法裡面,進行登錄和注冊操作,當操作成功後又通過協議方法通知委托對象。 getUsername() -> String?、getPassword() -> String?、 areBothUsernameAndPasswordFilled() -> Bool 和 cleanTextFields() 方法:是4個助手方法,用於讀取兩個 TextField 的輸入內容,清空它們,檢查它們的內容是否為空。

使用 Syncano 的 SCUser 類進行用戶登錄和注冊的兩個方法:

//MARK - Syncano
extension LoginViewController {
func login(username: String, password: String, finished: (NSError!) -> ()) {
    SCUser.loginWithUsername(username, password: password) { error in
        finished(error)
    }
}

func signUp(username: String, password: String, finished: (NSError!) -> ()) {
    SCUser.registerWithUsername(username, password: password) { error in
        finished(error)
    }
}
}

如你所見,整個過程很簡單——提供兩個參數:用戶名、密碼,如果操作不成功,返回一個錯誤(例如 password was incorrect for login 或者 user already exists for signup 等)。

修改ViewController

新的特性馬上就可以用了。萬事俱備,只欠東風,我們只需要把所有東西連接起來就 OK 了。

使用 LoginViewController

在 ViewController 中,我們會根據用戶登錄狀態來決定是否要顯示 LoginViewController。首先需要一個 LoginViewController 引用。新增一個屬性:

let loginViewController = UIStoryboard(name: “Main”, bundle: nil).instantiateViewControllerWithIdentifier(loginViewControllerIdentifier) as! LoginViewController

實現 viewDidAppear 方法——這個方法在視圖顯示到屏幕之後調用。在這個方法中檢查用戶是否已經登錄,如果用戶未登錄,則顯示登錄界面:

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.showLoginViewControllerIfNotLoggedIn()
    }






因為我們還沒實現 self.showLoginViewControllerIfNotLoggedIn() 方法,我們需要來實現它。增加一個新的擴展:

//MARK - Login Logic
extension ViewController : LoginDelegate {
    func didSignUp() {
        self.prepareAppForNewUser()
        self.hideLoginViewController()

    }

    func didLogin() {
        self.prepareAppForNewUser()
        self.hideLoginViewController()
    }

    func prepareAppForNewUser() {
        self.setupSenderData()
        self.reloadAllMessages()
    }

    func isLoggedIn() -> Bool {
        let isLoggedIn = (SCUser.currentUser() != nil)
        return isLoggedIn
    }

    func logout() {
        SCUser.currentUser()?.logout()
    }

    func showLoginViewController() {
        self.presentViewController(self.loginViewController, animated: true) {

        }
    }

    func hideLoginViewController() {
        self.dismissViewControllerAnimated(true) {

        }
    }

    func showLoginViewControllerIfNotLoggedIn() {
        if (self.isLoggedIn() == false) {
            self.showLoginViewController()
        }
    }

    @IBAction func logoutPressed(sender: UIBarButtonItem) {
        self.logout()
        self.showLoginViewControllerIfNotLoggedIn()
    }
}





這段代碼做了些什麼?

實現了前面定義的 LoginProtocol 協議(這樣,ViewController 會在用戶登錄或注冊新賬號之後得到通知) 幾個助手方法,檢查用戶是否登錄(通過判斷 SCUser.currentUser() 方法返回值是否為 nil),顯示或隱藏登錄界面,用於處理登出(只需要在當前用戶上調用 logout 方法)兩個方法。

修改 setup 擴展

這裡會出現兩個錯誤。我們需要定義兩個方法:

setupSenderData() 方法:根據用戶登錄信息修改發送者信息(替代早先我們使用的設備唯一標識 reloadAllMessages() 方法:重新下載最新的聊天消息並顯示給用戶。

在 ViewController 中找到 setup 擴展(這個擴展的開頭使用了 “// MARK - Setup” 進行注釋),將它修改為:

//MARK - Setup
extension ViewController {
    func setup() {
        self.title = "Syncano ChatApp"
        self.setupSenderData()
        self.channel.delegate = self
        self.channel.subscribeToChannel()
        self.loginViewController.delegate = self
    }

    func setupSenderData() {
        let sender = (SCUser.currentUser() != nil) ? SCUser.currentUser().username : ""
        self.senderId = sender
        self.senderDisplayName = sender
    }
}





這裡,我們添加了缺失的 setupSenderData() 方法,在這個方法中將 senderId 和 senderDisplayName 設置為 Syncano 中的用戶名。在Syncano 中,用戶名是唯一的,因此可以用作 senderId。

在 setup() 方法中,我們設置了 title,將 LoginViewController 的 delegate 設置為 self(我們已經為 self 實現了委托協議),然後調用 setupSenderData() 方法來設置發送者信息。

(我們刪除了在第一部分中添加測試消息的方法——我們已經用不著它了)

最後,我們還要實現 reloadAllMessages() 方法:

    func reloadAllMessages() {
        self.messages = []
        self.reloadMessagesView()
        self.downloadNewestMessagesFromSyncano()
    }





要重新加載聊天消息,只需要將 messages 數組清空,重新刷新消息視圖,避免數據源和消息視圖之間的不一致,然後從 Syncano 下載最新的消息。

最後就只剩下一塊了。找到 ViewController 的數據源擴展,添加兩個方法:

    override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {
        let data = self.collectionView(self.collectionView, messageDataForItemAtIndexPath: indexPath)
        if (self.senderDisplayName == data.senderDisplayName()) {
            return nil
        }
        return NSAttributedString(string: data.senderDisplayName())
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
        let data = self.collectionView(self.collectionView, messageDataForItemAtIndexPath: indexPath)
        if (self.senderDisplayName == data.senderDisplayName()) {
            return 0.0
        }
        return kJSQMessagesCollectionViewCellLabelHeightDefault
    }

第一個方法返回每條消息的發送者名稱——用於表明這條消息是誰發的。而對於我們自己發出的消息,我們不需要顯示這個名稱——我們知道自己所發的消息,因為這些消息總是位於右邊(其他人的消息位於左邊),因此沒有必要浪費空間了。

第二個方法定義了發送者名稱 Label 的高度。如果我們要顯示發送者名稱(發送者不是自己的消息),我們需要返回一個默認高度(返回 JSQMessagesViewController 中的一個常量), 否則返回 0,Label 就不會顯示。

修改故事板

在你運行 App 之前,還剩最後一件事:

修改故事板,添加一個 Login View Controller。 在 ViewController 上添加一個 logout 登出按鈕,以便在運行時切換用戶。

添加導航控制器

打開 Main.storyboard 文件,加入一個導航控制器。
\

拖一個 Navigation Controller 到故事板中。刪除它自帶的根 ViewController(通過 Delete 或 Backspace 鍵)。將初始控制器入口(藍色箭頭)拖到導航控制器上,在導航控制器上右鍵,拖一條線到 ViewController 上。在彈出菜單中,選擇 root view controller。

添加登出按鈕

\vcz1yc+ho8uru/ewtMWlo6y9q8v8tcTD+9fWuMSzySBMb2dvdXSho9TasLTFpcnP09K796Oszc/Su8z1z9+1vdfzsd8gT3V0bGluZSC0sL/atcQgVmlld0NvbnRyb2xsZXKjrNTata+z9rLLtaXW0NGh1PEgbG9naW5QcmVzc2VkOqOs1eLR+b7NvauwtMWlysK8/sGsvdO1vcHLyrXP1rao0uW1xLe9t6jJz6GjPC9wPg0KPGgzIGlkPQ=="添加登錄界面">添加登錄界面

\

拖一個 View Controller 到故事板中。選中這個 View Controller,在 Identity 面板中將 Class 設置為 LoginViewController。將 Storyboard ID 也設置為同樣值。

然後,拖 2 個 Text Fields 和 2 個 Button 到這個 View Controller,設置 TextField 的 Placeholder 屬性和按鈕的 Title 屬性。

右鍵,從Login View Controller 拖一條線到兩個 Text Field,將兩個 TextField 連接到相應的出口。

右鍵,從兩個按鈕拖一條線到 Login View Controller,將兩個按鈕的 Action 連接到相應的方法。

添加約束

\

在故事板中,依次選擇 LoginViewController 上的 UI 元素並設置它們的約束。這裡,我讓它們依次和最近的元素等間距對齊,並設置了固定高度(使用默認值)。

好了!你可以運行程序,它的所有功能都能使用了!

你可以運行多個 App 實例,登錄多個賬號並查看這個實時聊天 App 是否工作正常。

還有一個 Bug——如果你用同一個賬號登錄在兩台設備上登錄,如果在其中一台上發消息,則在另一台上不會立馬顯示這條消息。如果你能在 12 月 20 號前將解決辦法發送給我們,我們會提供一個小禮物給你!

你可以將答案、思路或代碼發送到 email 地址 [email protected]!

總結

你已經完成了本教程的第三部分,你的 App 已經可以使用了。你可以將它分發給你的朋友們,根據是否由你發送還是由別人發送,消息的顯示是截然不同的。

這部分的代碼在這裡下載。

如果你有任何問題,請立即在下面的評論中留言,或者 tweet 我。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved