Skip to content

Creating a Custom Style

Stephen Radford edited this page Jul 3, 2018 · 7 revisions

Sometimes tweaking an existing style doesn't provide enough flexibility. When that's the case you can create a custom style that loads its own collection view, cells, and input view. This gives you complete control over what your MSGMessengerViewController looks like without having to write everything from scratch.

Creating a Custom MSGMessengerStyle

As we learnt in Customising an Existing Style, styles are structs that adhere to the MSGMessengerStyle. Creating a custom style/theme is a case of creating a struct and ensuring it complies with that protocol.

Let's take a look at the following custom style:

struct CustomStyle: MSGMessengerStyle {
    
    var collectionView: MSGCollectionView.Type = CustomCollectionView.self
    
    var inputView: MSGInputView.Type = MSGImessageInputView.self
    
    var headerHeight: CGFloat = 0
    
    var footerHeight: CGFloat = 0
    
    var backgroundColor: UIColor = UIColor(hue:0.64, saturation:0.24, brightness:0.21, alpha:1.00)
    
    var inputViewBackgroundColor: UIColor = UIColor(hue:0.63, saturation:0.14, brightness:0.29, alpha:1.00)
    
    var font: UIFont = .preferredFont(forTextStyle: .body)
    
    var inputFont: UIFont = .systemFont(ofSize: 14)
    
    var inputPlaceholder: String = "Hello world..."
    
    var inputTextColor: UIColor = .darkText
    
    var inputPlaceholderTextColor: UIColor = .lightGray
    
    var outgoingTextColor: UIColor = .white
    
    var outgoingLinkColor: UIColor = .white
    
    var incomingTextColor: UIColor = .white
    
    var incomingLinkColor: UIColor = .white
    
    func size(for message: MSGMessage, in collectionView: UICollectionView) -> CGSize {
        var size: CGSize!
        
        switch message.body {
        case .text(let body):
            
            let bubble = CustomBubble()
            bubble.text = body
            bubble.font = font
            let bubbleSize = bubble.calculatedSize(in: CGSize(width: collectionView.bounds.width, height: .infinity))
            size = CGSize(width: collectionView.bounds.width, height: bubbleSize.height)
            
            break
            
            
        case .emoji:
            
            size = CGSize(width: collectionView.bounds.width, height: 60)
            
            break
            
        default:
            
            size = CGSize(width: collectionView.bounds.width, height: 175)
            
            break
        }
        
        return size
    }
    
    // MARK - Custom Properties
    
    var incomingBorderColor: UIColor = .white
    
    var outgoingBorderColor: UIColor = UIColor(hue:0.91, saturation:0.70, brightness:0.85, alpha:1.00)
    
}

There's a few things to note here. Most importantly you can see that the collectionView property has a value of CustomCollectionView.self. This is our custom MSGCollectionView that we'll take a deeper look at in a moment.

The inputView uses the included MSGImessageInputView and MSGTravInputView is also an option that can be used here. However if you want to roll your own input that's totally possible. You'll need to subclass MSGInputView and ensure a MSGPlaceholderTextView is used as the main text view.

Within our CustomMessengerStyle you'll also notice we've included a couple of custom properties for incomingBorderColor and outgoingBorderColor. You can add any properties you wish to your custom style and utilise this within your collection view, input view, or cells to style it as required.

Creating a Custom MSGCollectionView

If your design requires custom cell, header, or footer layouts then you'll need to subclass MSGCollectionView to achieve this. However, if you only wish to override one aspect of an existing design (say the cell design from MSGImessageCollectionView) then you can also subclass those classes too.

import UIKit
import MessengerKit

class CustomCollectionView: MSGImessageCollectionView {

    override func registerCells() {
        super.registerCells()
        
        register(UINib(nibName: "CustomOutgoingTextCell", bundle: nil), forCellWithReuseIdentifier: "outgoingText")
        register(UINib(nibName: "CustomIncomingTextCell", bundle: nil), forCellWithReuseIdentifier: "incomingText")
    }

}

In this example we've subclassed MSGImessageCollectionView and overridden the outgoingText and incomingText cells with our own UINib for that identifier. Like any UICollectionView this replaces the previously registered cell with our new one.

You can register any of the following cells:

  • outgoingText
  • incomingText
  • outgoingEmoji
  • incomingEmoji
  • outgoingImage
  • incomingImage
  • outgoingVideo
  • incomingVideo
  • outgoingCustom
  • incomingCustom

You can choose to register all cells or if you're only using one message type (e.g. text) then only that cell is required to be registered.

The following reusable views can also be registered:

  • outgoingHeader
  • incomingHeader
  • outgoingFooter
  • incomingFooter

Creating a Custom MSGMessageCell

A cell registered with an MSGCollectionView must be a subclass of MSGMessageCell. This includes some extra sugar such as automatic setup of long-press and tap gestures that bubble up through to the MSGDelegate.

However, what you choose to place into your cell and how you style it is entirely your domain. MSGMessageCell includes a number of properties that you can override, and with the help of a didSet mould the cell to fit your needs.

Here's the CustomTextCell from the example app:

class CustomTextCell: MSGMessageCell {

    @IBOutlet weak var bubble: CustomBubble!
    
    @IBOutlet weak var bubbleWidthConstraint: NSLayoutConstraint!
    
    @IBOutlet weak var avatarView: UIImageView?
    
    override open var message: MSGMessage? {
        didSet {
            guard let message = message,
                case let MSGMessageBody.text(body) = message.body else { return }
            
            bubble.text = body
            avatarView?.image = message.user.avatar
        }
    }
    
    override var style: MSGMessengerStyle? {
        didSet {
            guard let style = style as? CustomStyle, let message = message else { return }
            bubble.layer.borderColor = message.user.isSender ? style.outgoingBorderColor.cgColor : style.incomingBorderColor.cgColor
            bubble.linkTextAttributes[NSAttributedStringKey.underlineColor.rawValue] = style.outgoingLinkColor
            bubble.linkTextAttributes[NSAttributedStringKey.foregroundColor.rawValue] = style.outgoingLinkColor
            bubble.font = style.font
            bubble.textColor = message.user.isSender ? style.outgoingTextColor : style.incomingTextColor
        }
    }
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        let bubbleSize = bubble.calculatedSize(in: bounds.size)
        bubbleWidthConstraint.constant = bubbleSize.width
    }
    
    
    override func awakeFromNib() {
        super.awakeFromNib()
        avatarView?.layer.cornerRadius = 24
        avatarView?.layer.masksToBounds = true
    }
    
}

You'll notice that we use the didSet observer in both the message and style properties to correctly style and assign data to our cell.

Using Your Custom Style

The last thing you'll need to do to use the custom style is override the style property on your MSGMessengerViewController subclass and return an instance of it.

override var style: MSGMessengerStyle {
    return CustomStyle()
}

With that done, your messenger should now take on your custom style!

MessengerKit Custom Style