Messagekit: [Question] How to customize cellTopLabel?

Created on 5 Sep 2018  路  14Comments  路  Source: MessageKit/MessageKit

  • What version of MessageKit are you using?
    1.0.0
  • What version of iOS are you running on?
    iOS 10+
  • What version of Swift are you running on?
    4.1

I've run the example project and I noticed that it uses cellTopLabel to show grouped date time.

Is there any way to customize the cellTopLabel, for example: change background, put text inside rounded-corner rect, decorate with images?

Or maybe I should use sectionHeaderView to achieve my goal?

question

Most helpful comment

@tobitech you need to create your own reusable header view, not using cellTopLabel.

  1. Create your header view which subclass MessageReusableView.
class HeaderReusableView: MessageReusableView {
    // MARK: - Private Properties
    static private let attributes: [NSAttributedStringKey: Any] = [
        .font: UIFont.systemFont(ofSize: 12),
        .foregroundColor: UIColor.white
    ]
    static private let insets = UIEdgeInsets(top: 12, left: 80, bottom: 12, right: 80)

    private var label: UILabel!

    // MARK: - Public Methods
    static var height: CGFloat {
        return insets.top + insets.bottom + 27
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        createUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        createUI()
    }

    /// Setup the receiver with text.
    ///
    /// - Parameter text: The text to be displayed.
    func setup(with text: String) {
        label.attributedText = NSAttributedString(string: text, attributes: ChatHeaderReusableView.attributes)
    }

    override func prepareForReuse() {
        label.attributedText = nil
    }

    // MARK: - Private Methods
    private func createUI() {
        let insets = HeaderReusableView.insets
        let frame = UIEdgeInsetsInsetRect(bounds, insets)
        label = UILabel(frame: frame)
        label.preferredMaxLayoutWidth = frame.width
        label.numberOfLines = 1
        label.textAlignment = .center
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        label.backgroundColor = Theme.darkColor
        label.layer.cornerRadius = 13
        label.clipsToBounds = true
        addSubview(label)
    }
}
  1. Register it as messagesCollectionView's reusable header view.
messagesCollectionView.register(HeaderReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader)
  1. In MessagesDisplayDelegate
    func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView {
        let header = messagesCollectionView.dequeueReusableHeaderView(HeaderReusableView.self, for: indexPath)
        if (should show header view) {
            let message = messageForItem(at: indexPath, in: messagesCollectionView)
            header.setup(with: MyCustomizedString(from: message.sentDate))
        }
        return header
    }
  1. In MessagesLayoutDelegate
    func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
        if (should show header view) {
            return CGSize(width: messagesCollectionView.bounds.width, height: HeaderReusableView.height)
        } else {
            return .zero
        }
    }

That's it.

By the way, @SD10 , why the function in MessagesLayoutDelegate is headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView)? Shouldn't headerViewSize(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) be better?

See the different? I suggest it should be indexPath instead of section.

All 14 comments

Hi @chiahsien. I recommend adding a custom section header or overriding cellForRowAt, get the super cell and then make your changes.

Sent with GitHawk

@chiahsien

Is there any way to customize the cellTopLabel, for example: change background, put text inside rounded-corner rect, decorate with images?

You may want to try using NSTextAttachment. You can actually put images inside of an NSAttributedString. You can find the method to return the text for the cellTopLabel here

I recommend adding a custom section header or overriding cellForRowAt, get the super cell and then make your changes.

Thanks @nathantannar4

@SD10

You may want to try using NSTextAttachment.

But how to do something like "put text inside a rounded corner rect and filled background with specific color"?

@chiahsien Yeah there's no good way to do that unfortunately. The solution by Nathan is probably your best bet if you're looking to do that amount of customization

Understand, I'll give it a try. Thank you all!

were you able to achieve this @chiahsien please do share

Not yet, will try it later this or next week.

Hi @tobitech ,

This approach works, you can do it like how you handle headerView in UITable/CollectionView.

demo

@chiahsien so which of the MessagesViewController would i override, currently i'm overriding cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? it returns an attributed string not a UIView? besides MessageKit has a layout delegate that sets header so which one would i override? Please if you could show me some code on how you achieved it

@tobitech you need to create your own reusable header view, not using cellTopLabel.

  1. Create your header view which subclass MessageReusableView.
class HeaderReusableView: MessageReusableView {
    // MARK: - Private Properties
    static private let attributes: [NSAttributedStringKey: Any] = [
        .font: UIFont.systemFont(ofSize: 12),
        .foregroundColor: UIColor.white
    ]
    static private let insets = UIEdgeInsets(top: 12, left: 80, bottom: 12, right: 80)

    private var label: UILabel!

    // MARK: - Public Methods
    static var height: CGFloat {
        return insets.top + insets.bottom + 27
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        createUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        createUI()
    }

    /// Setup the receiver with text.
    ///
    /// - Parameter text: The text to be displayed.
    func setup(with text: String) {
        label.attributedText = NSAttributedString(string: text, attributes: ChatHeaderReusableView.attributes)
    }

    override func prepareForReuse() {
        label.attributedText = nil
    }

    // MARK: - Private Methods
    private func createUI() {
        let insets = HeaderReusableView.insets
        let frame = UIEdgeInsetsInsetRect(bounds, insets)
        label = UILabel(frame: frame)
        label.preferredMaxLayoutWidth = frame.width
        label.numberOfLines = 1
        label.textAlignment = .center
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        label.backgroundColor = Theme.darkColor
        label.layer.cornerRadius = 13
        label.clipsToBounds = true
        addSubview(label)
    }
}
  1. Register it as messagesCollectionView's reusable header view.
messagesCollectionView.register(HeaderReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader)
  1. In MessagesDisplayDelegate
    func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView {
        let header = messagesCollectionView.dequeueReusableHeaderView(HeaderReusableView.self, for: indexPath)
        if (should show header view) {
            let message = messageForItem(at: indexPath, in: messagesCollectionView)
            header.setup(with: MyCustomizedString(from: message.sentDate))
        }
        return header
    }
  1. In MessagesLayoutDelegate
    func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
        if (should show header view) {
            return CGSize(width: messagesCollectionView.bounds.width, height: HeaderReusableView.height)
        } else {
            return .zero
        }
    }

That's it.

By the way, @SD10 , why the function in MessagesLayoutDelegate is headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView)? Shouldn't headerViewSize(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) be better?

See the different? I suggest it should be indexPath instead of section.

@chiahsien works perfectly. THANKS

By the way, @SD10 , why the function in MessagesLayoutDelegate is headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView)? Shouldn't headerViewSize(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) be better?

See the different? I suggest it should be indexPath instead of section.

@chiahsien I guess that isn't indexPath because we are using only one item by section in MessageKit. Then, we will be only one section by message.

Refering to customization of cellTopLabel, I just want to add 1 more property label.accessibilityTraits = [.header] in:

    open var cellTopLabel: InsetLabel = {
        let label = InsetLabel()
        label.numberOfLines = 0
        label.textAlignment = .center
        return label
    }()

Which is found in open class MessageContentCell. Any help please? I do not want to unlock pod file.

hi, in ios 14 the label height is 0, can u help?
May be line of code cause this problem:

UIEdgeInsetsInsetRect(bounds, insets)
Was this page helpful?
0 / 5 - 0 ratings