Skip to content

Getting Started

Stephen Radford edited this page Jul 26, 2018 · 10 revisions

At the core of MessengerKit is MSGMessengerViewController. This should be subclassed within your app and used as the base for your app's messenger.

MSGMessengerViewController

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:

Default MSGMessengerViewController

There's a lot to break down here so let's take a look at some of the components here separately.

MSGMessage

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 the customOutgoing and customIncoming 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())

MSGDataSource

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:

Section Construction

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
    }

}

Handling Actions

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.

Override Methods

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?()
        }
    })
    
}

MSGDelegate

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
    }
    
}

What Next?

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.