1

I am trying to create custom cell for messageKi, following instruction but have an error:

Thread 1: "Invalid update: invalid number of sections. The number of sections contained in the collection view after the update (1) must be equal to the number of sections contained in the collection view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted). Collection view: <MessageKit.MessagesCollectionView: 0x13588b000; baseClass = UICollectionView; frame = (0 0; 0 0); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6000017dda70>; backgroundColor = <UIDynamicCatalogColor: 0x600003a3c410; name = collectionViewBackground>; layer = <CALayer: 0x600001906220>; contentOffset: {0, 0}; contentSize: {0, 0}; adjustedContentInset: {0, 0, 0, 0}; layout: <MesKit.MyCustomMessagesFlowLayout: 0x133b27370>; dataSource: (null)>"

There is my full code:

import UIKit
import MessageKit
import InputBarAccessoryView

struct UserSender: SenderType {
    var senderId: String

    var displayName: String
}

struct MessageChat: MessageType {
    var sender: MessageKit.SenderType

    var messageId: String

    var sentDate: Date

    var kind: MessageKit.MessageKind
}

class ChatViewController: MessagesViewController {

    lazy var messageList: [MessageChat] = []
    // MARK: Injections
    var currentUser = UserSender(senderId: "0", displayName: "Cur")
    var notCurrentUser = UserSender(senderId: "1", displayName: "NotCur")

    // MARK: View lifeCycle
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        configureMessageCollectionView()
        configureMessageInputBar()
        
        messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MyCustomMessagesFlowLayout())
        messagesCollectionView.register(MyCustomCell.self)
        
        if let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout {
            layout.textMessageSizeCalculator.outgoingAvatarSize = .zero
            layout.textMessageSizeCalculator.incomingAvatarSize = .zero
            layout.setMessageIncomingAvatarSize(.zero)
        }
    }

    override open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
            fatalError("Ouch. nil data source for messages")
        }
        //before checking the messages check if section is reserved for typing otherwise it will cause IndexOutOfBounds error
        if isSectionReservedForTypingIndicator(indexPath.section){
            return super.collectionView(collectionView, cellForItemAt: indexPath)
        }
        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
        if case .custom = message.kind {
            let cell = messagesCollectionView.dequeueReusableCell(MyCustomCell.self, for: indexPath)
            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
            return cell
        }
        return super.collectionView(collectionView, cellForItemAt: indexPath)
    }

    func configureMessageCollectionView() {
        messagesCollectionView.messagesDataSource = self
        messagesCollectionView.messageCellDelegate = self
        messagesCollectionView.messagesLayoutDelegate = self
        messagesCollectionView.messagesDisplayDelegate = self

        scrollsToLastItemOnKeyboardBeginsEditing = true
        maintainPositionOnInputBarHeightChanged = true
        showMessageTimestampOnSwipeLeft = true
      }

      func configureMessageInputBar() {
        messageInputBar.delegate = self
          messageInputBar.inputTextView.tintColor = .cyan
          messageInputBar.sendButton.setTitleColor(.purple, for: .normal)
        messageInputBar.sendButton.setTitleColor(
            .purple,
          for: .highlighted)
      }
    func textCell(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UICollectionViewCell? {
       nil
     }

    func insertMessage(_ message: MessageChat) {
       messageList.append(message)
       messagesCollectionView.performBatchUpdates({
         messagesCollectionView.insertSections([messageList.count - 1])
         if messageList.count >= 2 {
           messagesCollectionView.reloadSections([messageList.count - 2])
         }
       }, completion: { [weak self] _ in
         if self?.isLastSectionVisible() == true {
           self?.messagesCollectionView.scrollToLastItem(animated: true)
         }
       })
     }

    func isLastSectionVisible() -> Bool {
        guard !messageList.isEmpty else { return false }

        let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)

        return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
      }
    
    func setTypingIndicatorViewHidden(_ isHidden: Bool, message: MessageChat? = nil, performUpdates updates: (() -> Void)? = nil) {
        setTypingIndicatorViewHidden(isHidden, animated: true, whilePerforming: updates) { [weak self] success in
            if success {
                if message != nil {
                    self?.messageList.append(message!)
                }
                self?.messagesCollectionView.reloadData()
            }
        }
    }
}

extension ChatViewController: MessagesDataSource {
    var currentSender: MessageKit.SenderType {
        currentUser
    }
    
    func messageForItem(
        at indexPath: IndexPath,
        in messagesCollectionView: MessagesCollectionView) -> MessageType {
            messagesCollectionView.layer.zPosition = 9
            if messageList.count == indexPath.section && messageList.count > 0 && indexPath.section > 0 {
                return messageList[indexPath.section-1]
            }
            return messageList[indexPath.section]
        }
    
    func numberOfSections(in messagesCollectionView: MessageKit.MessagesCollectionView) -> Int {
        messageList.count
    }
}

extension ChatViewController: MessagesDisplayDelegate {
    func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
        let item = messageList[indexPath.section]
        if item.sender.senderId == currentUser.senderId {
            return .purple
        }
        return .gray
    }

    func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
        .black
    }
}

extension ChatViewController: InputBarAccessoryViewDelegate {
    func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
        inputBar.invalidatePlugins()
        inputBar.sendButton.startAnimating()
        inputBar.inputTextView.placeholder = "Sending..."
        inputBar.inputTextView.resignFirstResponder()
        DispatchQueue.global(qos: .default).async {
            sleep(1)
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                inputBar.sendButton.stopAnimating()
                inputBar.inputTextView.placeholder = "Aa"
                inputBar.inputTextView.text = ""
                self.insertMessage(self.createCustomMessage(text: text))
                self.messagesCollectionView.scrollToLastItem(animated: true)
            }
        }
    }

    private func createMessage(isMe: Bool, text: String) -> MessageChat {
        return MessageChat(
            sender: isMe ? currentUser : notCurrentUser,
            messageId: UUID().uuidString,
            sentDate: Date(),
            kind: .text(text)
        )
    }
    
    private func createCustomMessage(text: String) -> MessageChat {
        return MessageChat(sender: currentUser, messageId: UUID().uuidString, sentDate: Date(), kind: .custom([text, text, text, text]))
    }
}
// MARK: - ChatPresenterOutput
extension ChatViewController {
    private func insertMessages(_ data: [Any]) {
        for component in data {
            let user = self.currentUser
            if let str = component as? String {
                let message = MessageChat(sender: user, messageId: UUID().uuidString, sentDate: Date(), kind: .text(str))
                insertMessage(message)
            }
        }
    }
}

open class MyCustomCell: UICollectionViewCell {
    open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
        self.contentView.backgroundColor = UIColor.red
    }
}


open class CustomMessageSizeCalculator: MessageSizeCalculator {
    open override func messageContainerSize(for _: MessageType, at _: IndexPath) -> CGSize {
        return CGSize(width: 300, height: 130)
    }
}

open class MyCustomMessagesFlowLayout: MessagesCollectionViewFlowLayout {
    lazy open var customMessageSizeCalculator = CustomMessageSizeCalculator(layout: self)
    override open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
        if isSectionReservedForTypingIndicator(indexPath.section) {
            return typingIndicatorSizeCalculator
        }
        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
        if case .custom = message.kind {
            return customMessageSizeCalculator
        }
        return super.cellSizeCalculatorForItem(at: indexPath);
    }
}

How can i avoid that error?

Tried this but it does not work for me: https://github.com/MessageKit/MessageKit/issues/1788

PavelPud
  • 31
  • 3

0 Answers0