-
Notifications
You must be signed in to change notification settings - Fork 127
Getting Started
At the core of MessengerKit is MSGMessengerViewController
. This should be subclassed within your app and used as the base for your app's messenger.
Let's take a quick look at the most basic usage of MSGMessengerViewController
import UIKit
import MessengerKit
class ViewController: MSGMessengerViewController {
// Users in the chat
let steve = User(displayName: "Steve", avatar: #imageLiteral(resourceName: "steve228uk"), avatarUrl: nil, isSender: true)
let tim = User(displayName: "Tim", avatar: #imageLiteral(resourceName: "timi"), avatarUrl: nil, isSender: false)
// Messages
lazy var messages: [[MSGMessage]] = {
return [
[
MSGMessage(id: 1, body: .emoji("🐙💦🔫"), user: tim, sentAt: Date()),
],
[
MSGMessage(id: 2, body: .text("Yeah sure, gimme 5"), user: steve, sentAt: Date()),
MSGMessage(id: 3, body: .text("Okay ready when you are"), user: steve, sentAt: Date())
]
]
}()
// Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
}
}
// MARK: - MSGDataSource
extension ViewController: MSGDataSource {
func numberOfSections() -> Int {
return messages.count
}
func numberOfMessages(in section: Int) -> Int {
return messages[section].count
}
func message(for indexPath: IndexPath) -> MSGMessage {
return messages[indexPath.section][indexPath.item]
}
func footerTitle(for section: Int) -> String? {
return "Just now"
}
func headerTitle(for section: Int) -> String? {
return messages[section].first?.user.displayName
}
}
This will produce a messenger that looks something like this:
There's a lot to break down here so let's take a look at some of the components here separately.
We'll go over this a little more in the next section but it's important to understand the structure of a message before we continue.
Every message in MessengerKit is represented by the MSGMessage
model. This class takes a few properties to provide the relevant information to the MSGMessengerViewController
.
Here's the source of the model.
/// Represents a message within MessengerKit.
public class MSGMessage: NSObject{
/// A unique identifier for the message.
/// This is used to cache bubble sizes for the messenger.
public let id: Int
/// The body of the message.
public let body: MSGMessageBody
/// The user that sent the message.
public let user: MSGUser
/// The time that the message was sent.
public let sentAt: Date
public init(id: Int, body: MSGMessageBody, user: MSGUser, sentAt: Date) {
self.id = id
self.body = body
self.user = user
self.sentAt = sentAt
}
}
Some of these properties like id
and sentAt
are explained by the comments but let's go over user
and body
in a little more detail.
You'll notice that user
is an MSGUser
. MessengerKit doesn't impose its own user models onto you but instead allows you to bring your own. However, you do need to ensure your user models comply with the MSGUser
protocol which includes some sensible parameters required by MessengerKit.
/// Objects representing a user within MessengerKit
/// Must conform to this protocol.
public protocol MSGUser {
/// The name that will be displayed on the cell
var displayName: String { get }
/// The avatar for the user.
/// This is optional as an `avatarUrl` can be provided instead.
var avatar: UIImage? { get set }
/// The URL for an avatar.
/// This is optional as an `avatar` can be provided instead.
var avatarUrl: URL? { get set }
/// Whether this user is the one sending messages.
/// This is used to determine which bubble is rendered.
var isSender: Bool { get }
}
The body
is an MSGMessageBody
which is an enum. This is because a message can currently be one of four types:
text(String)
emoji(String)
image(UIImage)
video(String, UIImage)
custom(Any)
Each of these types are rendered in their own way using separate cells so it's important to select the right case for the message you wish to display.
Note: the
custom(Any)
case is not used by any of the included themes by default. Either register thecustomOutgoing
andcustomIncoming
cells or create your own style that handles this.
Finally, here's how you would construct an MSGMessage
.
MSGMessage(id: 1, body: .text("Yeah sure, gimme 5"), user: steve, sentAt: Date())
To provide messages to the view controller you'll need to take advantage of the MSGDataSource
protocol. If you've used a UICollectionView
before then you'll feel right at home here.
You may have noticed the format of our messages array isn't flat.
lazy var messages: [[MSGMessage]] = {
return [
[
MSGMessage(id: 1, body: .emoji("🐙💦🔫"), user: tim, sentAt: Date()),
],
[
MSGMessage(id: 2, body: .text("Yeah sure, gimme 5"), user: steve, sentAt: Date()),
MSGMessage(id: 3, body: .text("Okay ready when you are"), user: steve, sentAt: Date())
]
]
}()
This is because messages are arranged into sections with each section having its own header and footer (style permitting). It's up to you how you wish to arrange your sections but splitting sections into groups of messages sent by a user at a given time seems to work well.
The breakdown of a section looks a little like this:
Now that we know this we can look at what each of the MSGDelegate
methods provide.
extension ViewController: MSGDataSource {
/// Provides the number of sections with the current chat
func numberOfSections() -> Int { }
/// Provides the number of messages within the current section
func numberOfMessages(in section: Int) -> Int { }
/// Provides the message for the requested IndexPath
func message(for indexPath: IndexPath) -> MSGMessage {
return messages[indexPath.section][indexPath.item]
}
/// Provides the text that should be displayed in the footer title.
/// Note: Not all styles include a footer.
func footerTitle(for section: Int) -> String? {
return "Just now"
}
/// Provides the text that should be displayed in the header title.
/// Note: Not all styles include a header.
func headerTitle(for section: Int) -> String? {
return messages[section].first?.user.displayName
}
}
It's likely you'll need to override a few methods on MSGMessengerViewController
to handle some actions but there is also a delegate (MSGDelegate
) with some extra methods you may find helpful.
There are three methods you may wish to override in in the view controller that are useful for handling new message insertion.
func inputViewPrimaryActionTriggered(inputView: MSGInputView)
This method is called when the MSGInputView
primary action has been triggered (by default this is when a user taps the send button). You can get the new message sent from inputView.message
and handle it as required.
There are also two helper methods that can be used to insert messages into the controller. By default these create a new section per message sent so you may wish to override this to adapt the functionality. The following checks the sender of the messages in the last section and inserts the message there instead if it matches the current sender.
override func insert(_ message: MSGMessage) {
collectionView.performBatchUpdates({
if let lastSection = self.messages.last, let lastMessage = lastSection.last, lastMessage.user.displayName == message.user.displayName {
self.messages[self.messages.count - 1].append(message)
let sectionIndex = self.messages.count - 1
let itemIndex = self.messages[sectionIndex].count - 1
self.collectionView.insertItems(at: [IndexPath(item: itemIndex, section: sectionIndex)])
} else {
self.messages.append([message])
let sectionIndex = self.messages.count - 1
self.collectionView.insertSections([sectionIndex])
}
}, completion: { (_) in
self.collectionView.scrollToBottom(animated: true)
self.collectionView.layoutTypingLabelIfNeeded()
})
}
override func insert(_ messages: [MSGMessage], callback: (() -> Void)? = nil) {
collectionView.performBatchUpdates({
for message in messages {
if let lastSection = self.messages.last, let lastMessage = lastSection.last, lastMessage.user.displayName == message.user.displayName {
self.messages[self.messages.count - 1].append(message)
let sectionIndex = self.messages.count - 1
let itemIndex = self.messages[sectionIndex].count - 1
self.collectionView.insertItems(at: [IndexPath(item: itemIndex, section: sectionIndex)])
} else {
self.messages.append([message])
let sectionIndex = self.messages.count - 1
self.collectionView.insertSections([sectionIndex])
}
}
}, completion: { (_) in
self.collectionView.scrollToBottom(animated: false)
self.collectionView.layoutTypingLabelIfNeeded()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
callback?()
}
})
}
The MSGDelegate
protocol contains a number of methods you can take advantage of. To use it assign a class that conforms to MSGDelegate
to the delegate
property on your MSGViewController
extension ViewController: MSGDelegate {
/// Called when a link is tapped in a message
func linkTapped(url: URL) {
print("Link tapped:", url)
}
/// Called when an avatar is tapped
func avatarTapped(for user: MSGUser) {
print("Avatar tapped:", user)
}
/// Called when a message is tapped
func tapReceived(for message: MSGMessage) {
print("Tapped: ", message)
}
/// Called when a message is long pressed
func longPressReceieved(for message: MSGMessage) {
print("Long press:", message)
}
/// When a link is tapped MessengerKit will first ask if
/// `SFSafariViewController` should be presented.
func shouldDisplaySafari(for url: URL) -> Bool {
return true
}
/// Should a link not be of the http scheme this method
/// will be called i.e. mail, tel etc.
func shouldOpen(url: URL) -> Bool {
return true
}
}
Check out the following docs for more on customising MessengerKit:
If you haven't already, check out the example project that demonstates the default usage of MessengerKit.