下載訊息

下載訊息的程式將分成兩個部分,第一個部分是下載歷史訊息,第二個部分則是取得最新訊息

下載歷史訊息的部分,在viewDidLoadmessageRef = FIRDatabase.database().reference().child("messages/(roomID!)")之後加入

        messageRef.queryOrdered(byChild: "date").observeSingleEvent(of: .value, with: {[weak self] (snapshot) in

            if !snapshot.hasChildren() { return }

            // messages 架構為
            // messageId1
            // |- messageData1
            // messageId2
            // |- messageData2
            snapshot.children.forEach({ (child) in
                guard let childSnap = child as? FIRDataSnapshot else { return }

                guard let msgData = childSnap.value as? [String: Any] else { return }

                let msgId = childSnap.key
                var message = msgData
                message["id"] = msgId
                self?.messages.append(message)
            })

            self?.collectionView?.reloadData()

            guard let interval = self?.messages.last?["date"] as? TimeInterval else { return }

            self?.messageRef.queryOrdered(byChild: "date").queryStarting(atValue: interval + 0.001).observe(.childAdded, with: {[weak self] (snapshot) in

                guard let msgData = snapshot.value as? [String: Any], let senderId = msgData["senderId"] as? String else {
                    return
                }

                // 如果是自己傳的訊息就跳過 (因未之前傳送訊息時就有加入dataSource了)
                if senderId == self?.senderId() {
                    return
                }

                var message = msgData
                message["id"] = snapshot.key

                self?.messages.append(message)
                self?.finishReceivingMessage()
            })
        })

接著還要實作JSQ中的dataSource才能夠正常顯示訊息,在程式碼最底下加入extension並將需要實作的func加入

extension ChatViewController {
    // 訊息數量
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return messages.count
    }

    // 將訊息資料轉換成JSQ指定的Model
    override func collectionView(_ collectionView: JSQMessagesCollectionView, messageDataForItemAt indexPath: IndexPath) -> JSQMessageData {
        let message = messages[indexPath.item]

        guard let text = message["text"] as? String,
            let senderId = message["senderId"] as? String,
            let senderDisplayName = message["senderDisplayName"] as? String,
            let interval = message["date"] as? TimeInterval
            else { fatalError("data format exception") }

        let date = Date(timeIntervalSince1970: interval)

        return JSQMessage(senderId: senderId, senderDisplayName: senderDisplayName, date: date, text: text)
    }

    // 泡泡框
    override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource? {
        // TODO: 訊息會自動分邊,但目前泡泡框都會只向右側,稍候修正
        return JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.green)
    }

    // 頭像 - 此範例不顯示頭像    
    override func collectionView(_ collectionView: JSQMessagesCollectionView, avatarImageDataForItemAt indexPath: IndexPath) -> JSQMessageAvatarImageDataSource? {
        return nil
    }
}

實作到這邊,如果有iOS裝置就可以將APP運行到裝置中,試著和模擬器互相傳送訊息

首先,讓泡泡框的方向正確,在func viewDidLoad()上方加入

    lazy var incomingBubbleImage: JSQMessageBubbleImageDataSource = {[unowned self] in
        let factory = JSQMessagesBubbleImageFactory()
        return factory.incomingMessagesBubbleImage(with: UIColor.darkGray)
    }()

    lazy var outgoingBubbleImage: JSQMessageBubbleImageDataSource = {[unowned self] in
        let factory = JSQMessagesBubbleImageFactory()
        return factory.outgoingMessagesBubbleImage(with: UIColor.lightGray)
    }()

並修改extension中泡泡框的dataSource

    override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource? {
        let message = messages[indexPath.item]

        if message["senderId"] as? String == self.senderId() {
            return outgoingBubbleImage
        } else {
            return incomingBubbleImage
        }
    }

修改後再次運行APP

因為聊天室可能一次有很多人進來,如果不標示的話使用者會難以辨識說話的對象,因此接下來需要幫incoming的訊息標上名字

extension中,追加下列func實作

    override func collectionView(_ collectionView: JSQMessagesCollectionView, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout, heightForMessageBubbleTopLabelAt indexPath: IndexPath) -> CGFloat {
        return 30
    }

    override func collectionView(_ collectionView: JSQMessagesCollectionView, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath) -> NSAttributedString? {

        let message = messages[indexPath.item]

        if message["senderId"] as? String == self.senderId() {
            return nil
        }

        guard let displayName = message["senderDisplayName"] as? String else {
            return NSAttributedString(string: "未知")
        }

        return NSAttributedString(string: displayName)
    }

成果圖

由於這個範例還沒有放上美美的頭貼,剛剛實作dataSource雖然已經告訴JSQMessagesViewController不提供頭貼,但是他還是幫我們保留了左右兩側顯示頭貼的位置,在viewDidLoad內最後面加入下列程式,可以將兩邊多餘的空白移除

        collectionView?.collectionViewLayout.incomingAvatarViewSize = .zero
        collectionView?.collectionViewLayout.outgoingAvatarViewSize = .zero

然而我們也還沒有實作傳送附加檔案功能,至時候如果去點了迴紋針圖示App將Crash,在上面兩行程式碼之後再加入下列程式來隱藏附加檔案按鈕

inputToolbar.contentView?.leftBarButtonItem = nil

別忘了還要在deinit時釋放observer

    deinit {
        messageRef.removeAllObservers()
    }

完全體

文字聊天功能到此完成,程式碼下載

results matching ""

    No results matching ""