-
Notifications
You must be signed in to change notification settings - Fork 126
Creating a Custom Style
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.
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.
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
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.
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!
