Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Need help] Adding a new cell type #686

Closed
adri4silva opened this issue May 16, 2018 · 27 comments
Closed

[Need help] Adding a new cell type #686

adri4silva opened this issue May 16, 2018 · 27 comments

Comments

@adri4silva
Copy link

I need a custom "notification" cell like this:

custom-cell

Some tips to help me working around in this idea?

I'm thinking to create a new NotificationCell class that will be child of UICollectionViewCell and then create NotificationsSizeCalculator, NotificationsCollectionViewFlowLayout and NotificationsCollectionViewLayoutAttributes

Is that a good approach?

Are you interested in that kind of feature?

Thank you in advance!

@adri4silva
Copy link
Author

Ok, I saw in other issues that the solution is to do this customization under .custom case.

Any tips about implementing a new custom cell?

@wildseansy
Copy link

wildseansy commented May 16, 2018

I got this to work by doing the following. Seems to basically be what you suggested.

Reference the project's example for the rest of ConversationViewController. This example renders a red block for your custom message.

ConversationViewController.swift
import UIKit
import MessageKit
internal class ConversationViewController: MessagesViewController {
    override func viewDidLoad() {
        messagesCollectionView = MessagesCollectionView(frame: .zero, collectionViewLayout: MyCustomMessagesFlowLayout())
        messagesCollectionView.register(MyCustomCell.self)
        super.viewDidLoad()
        //...
    }
//...
    override open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
            fatalError("Ouch. nil data source for messages")
        }
        
        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)
    }
}
MyCustomCell.swift
// Customize this collection view cell with data passed in from message, which is of type .custom
open class MyCustomCell: UICollectionViewCell {
    open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
        self.contentView.backgroundColor = UIColor.red
    }
    
}
MyCustomMessagesFlowLayout.swift
import Foundation
import MessageKit

open class MyCustomMessagesFlowLayout: MessagesCollectionViewFlowLayout {
    lazy open var customMessageSizeCalculator = CustomMessageSizeCalculator(layout: self)

    override open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
        if case .custom = message.kind {
            return customMessageSizeCalculator
        }
        return super.cellSizeCalculatorForItem(at: indexPath);
    }
}

open class CustomMessageSizeCalculator: MessageSizeCalculator {
    open override func messageContainerSize(for message: MessageType) -> CGSize {
         //TODO - Customize to size your content appropriately. This just returns a constant size.
        return CGSize(width: 300, height: 130)
    }
}

@SD10
Copy link
Member

SD10 commented May 16, 2018

@wildseansy This is an awesome example! It's exactly what's expected to create a custom cell 😄

@nathantannar4
Copy link
Member

👏 👏 👏

@adri4silva
Copy link
Author

That is what I was looking for! Thank you so much @wildseansy 😀

@fr-josh
Copy link

fr-josh commented May 18, 2018

@wildseansy, thank you for your solution, it's been very helpful to me. One final part I am stuck is on how I can place the custom cell on the right or the left based on who has sent the message. I thought the following would work but alas it does not have the desired effect:

class CustomMessageSizeCalculator: MessageSizeCalculator {
	
	override init(layout: MessagesCollectionViewFlowLayout?) {
		super.init(layout: layout)
		self.incomingMessagePadding = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: UIScreen.main.bounds.width - 294)
		self.outgoingMessagePadding = UIEdgeInsets(top: 0, left: UIScreen.main.bounds.width - 294, bottom: 0, right: 4)
	}
	
	override func sizeForItem(at indexPath: IndexPath) -> CGSize {
		return CGSize(width: 290, height: 194)
	}
	
}

@wildseansy
Copy link

wildseansy commented May 18, 2018

@fr-josh - it might be because you're overriding sizeForItem, self.incomingMessagePadding/self.outgoingMessagePadding won't be used properly unless you call super.sizeForItem, or use these values in your computation. See the superclass for more context. Looks like you might want to use messagesLayout.itemWidth for your width.

@SD10
Copy link
Member

SD10 commented May 28, 2018

@wildseansy Do you think you'd be up for the task of documenting your answer above in Documentation/FAQs.md? It would be a huge help to our project

@wildseansy
Copy link

@SD10 - sure no prob. Can submit sometime today/tomorrow

@SD10
Copy link
Member

SD10 commented May 28, 2018

@wildseansy No rush, at your own pace 👍

@artemkalinovsky
Copy link

Is there any way to load custom cell from .xib?🤔

@artemkalinovsky
Copy link

artemkalinovsky commented May 30, 2018

Found this workaround for my question:

import UIKit
import MessageKit
import SnapKit

class CustomChatMessageCell: UICollectionViewCell, BaseCellProtocol {

    func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {

        let customContentView = CutomMessageContentView.instantiateFromNib()!
        self.contentView.addSubview(customContentView)

        customContentView.snp.makeConstraints { make in
            make.top.bottom.leading.trailing.equalTo(customContentView.superview!)
        }

    }

}

@Luke47
Copy link

Luke47 commented Jun 6, 2018

I will just leave this here as it gave me a pretty big headache while implementing my subclass of MessageContentCell (I used this parent class instead of UICollectionViewCell like in the example code above because I still wanted to use all the extra features like top and bottom label, sizing and positioning for my custom cell). Maybe it is wise to add it to the FAQ too?

You are supposed to add your custom content view (in my case a LinkPreviewView) to the messageContainerView of your subclass of MessageContentCell so that all elements are positioned and sized correctly. However, MessageContainerView is actually a subclass of UIImageView, which has user interaction disabled per default! So if you add any textviews with hyperlinks, buttons or other interactable stuff you need to enable user interaction on the MessageContainerView in order for it to work properly.

Here is an example on how to do that (not the complete code for my subclass):

class LinkPreviewCollectionViewCell: MessageContentCell {

    open class func reuseIdentifier() -> String { return "messagekit.cell.linkPreview" }

    lazy open var linkPreviewView: LinkPreviewParentView = {
        return LinkPreviewParentView()
    }()
    
    open override func setupSubviews() {
        super.setupSubviews()

        self.messageContainerView.addSubview(linkPreviewView)
        // We need to enable user interaction on the MessageContainerView, since it is a UIImageView subclass, which has user interaction disabled per default
        self.messageContainerView.isUserInteractionEnabled = true
        self.setupConstraints()

    }
}

@stale
Copy link

stale bot commented Jun 20, 2018

This issue has been marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale label Jun 20, 2018
@stale
Copy link

stale bot commented Jun 27, 2018

This issue has been auto-closed because there hasn't been any activity for at least 21 days. However, we really appreciate your contribution, so thank you for that! 🙏 Also, feel free to open a new issue if you still experience this problem 👍.

@adelbios
Copy link

adelbios commented Jul 1, 2018

it's not working for me how ?!

@stshelton
Copy link

I went with @Luke47 method for creating a custom cell by subclassing MessageContentCell. So that I could still use those extra features like top and bottom label, sizing and positioning and the avatar view. When displaying my custom cell everything is correctly positioned but MesssageDataSource delegate only calls methods like "ConfigureAvatarView", and "cellTopLabelAttributedText" for the non custom cells. Yet "cellTopLabelHeight" and "messageForItem" is called everytime even for custom cells. Id like to use all of the features of MessageDatSource to be able to add timestamps to those custom cells, If you guys could point me in the right direction it would be much appreciated. Also, I did notice that within my subclass of MessageContentCell you can access there avatar view and labels, I would rather use the MessageDataSource Delegate to populate those fields though.

@tobitech
Copy link

tobitech commented Nov 7, 2018

This documentation hasn't still been added to the FAQs since May

@nathantannar4
Copy link
Member

Have you looked at the example project? I have an advanced example that shows this and more.

Sent with GitHawk

@wildseansy
Copy link

wildseansy commented Nov 7, 2018

@tobitech see here. Agree it might be worth pointing to this example from the FAQ tho

@tobitech
Copy link

tobitech commented Nov 7, 2018

Okay i've see that. What if I want to have more than one custom cells (say for Audio Message, Document Message, Contact Message), would i create CustomFlowLayouts for each of them and register different cells for each of them?

@wildseansy
Copy link

wildseansy commented Nov 7, 2018

@tobitech - You can just create one CustomFlowLayout.

You would need to inspect the .custom(Any?) parameter that is stored in the .custom struct type. Using this internal data of type Any, you can then parse out whether it's an audio, document, or contact message, and then determine what height/width to supply to the flow layout.

Alternatively, you can also extend your message model to be a little more specific for this custom case. This is how I implemented my custom cells, so it's a little clearer when something goes wrong:

/* MODELS */
internal enum ChatMessageSubtype {
    case none()
    case audio()
    case document(CGFloat)
    case contact()
}

internal struct MyChatMessage: MessageType {

    var messageId: String
    var sender: Sender
    var sentDate: Date
    var kind: MessageKind
    var subtype: ChatMessageSubtype

    private init(kind: MessageKind, sender: Sender, messageId: String, date: Date) {
        self.kind = kind
        self.subtype = .none()
        self.sender = sender
        self.messageId = messageId
        self.sentDate = date
    }
    /*
      You would initialize a custom message like so:
      
      MyChatMessage(kind: .custom(nil), subtype: .audio(), sender: senderObject, messageId: "<messageID>", date: Date())

    */
    init(kind: MessageKind, subtype: ChatMessageSubtype, sender: Sender, messageId: String, date: Date) {
        self.init(kind: kind, sender: sender, messageId: messageId, date: date)
        self.subtype = subtype
    }

    init(text: String, sender: Sender, messageId: String, date: Date) {
        self.init(kind: .text(text), sender: sender, messageId: messageId, date: date)
    }

    //...See MockMessage.swift for the rest of the model.
}

/* Flow layout */
open class CustomMessagesFlowLayout: MessagesCollectionViewFlowLayout {
    lazy open var customCellCalculator = CustomCellCalculator(layout: self)

    override open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
        if case .custom = message.kind {
            return customCellCalculator
        }
        return super.cellSizeCalculatorForItem(at: indexPath);
    }
    
}

open class CustomCellCalculator: MessageSizeCalculator {
    open override func messageContainerSize(for message: MessageType) -> CGSize {
        let maxWidth = messageContainerMaxWidth(for: message)
        let customMessage = message as! MyChatMessage
        var height:CGFloat = 130.0
        switch customMessage.subtype {
          case .audio():
              height = 50.0
              break
          case .document(let documentHeight):
              height = documentHeight
          case .contact():
              height = 60.0
          default: break
        }
        
        return CGSize(width: maxWidth, height: height)
    }
}

@tobitech
Copy link

tobitech commented Nov 7, 2018

Thanks would try it out

@umair2ooo
Copy link

umair2ooo commented Jan 21, 2019

pod 'MessageKit', '~> 2.0.0-beta.1'
Incoming and Outgoing messages padding issue still exists in Custom messages
There is no prominent Answer from anyone
Either Incoming or Outgoing message, Both showed in the middle of the screen
even after implementing the padding as people suggested
And there is no Audio message option like WhatsApp which has high precedence
I will be thankful if anyone will help me :-)

@kodboy
Copy link

kodboy commented Jul 2, 2019

you can do like this, add your view to messageContainerView

import MessageKit
import SnapKit

open class MyMessageCell: MessageContentCell {
    
    let myView = MyView()
    
    open override func setupSubviews() {
        super.setupSubviews()
        messageContainerView.addSubview(myView)
        myView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
    }
    
    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
        super.configure(with: message, at: indexPath, and: messagesCollectionView)
        // do something with `myView`
    }
 }

open class MyViewSizeCalculator: MessageSizeCalculator {
    
    public override init(layout: MessagesCollectionViewFlowLayout? = nil) {
        super.init()
        self.layout = layout
    }
    
    /// don't override this function
    open override func sizeForItem(at indexPath: IndexPath) -> CGSize {
        // do nothing
        return super.sizeForItem(at: indexPath)
    }
    
    /// override this function
    open override func messageContainerSize(for message: MessageType) -> CGSize {
        /// ur custom view size
        return CGSize(width: 200, height: 100)
    }
    
}

@nathantannar4
Copy link
Member

@umair2ooo Audio messages were added in 3.0

Sent with GitHawk

@vincenzo-favara
Copy link

is there a way to tap on the "milling call" button?
I have an issue like this, have a look here: #1227

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests