iMessage Stickers (Remote Images + Analytics)

Starting in WWDC 2016 with the release of iOS 10, Apple allowed iOS developers to create their own sticker extensions for the iMessage App Store. So, last week, I started to look into it; a little late to the game, I know.

Initially, I wanted to create a sticker pack whose contents I could remotely update. Moreover, I wanted to be able to track which sticker was the most popular and how many times a particular sticker was sent. Unfortunately, Apple doesn’t provide this functionality out of the box and makes it pretty tricky to implement it on your own.

Since the MSStickerBrowserView doesn’t provide any callbacks when a sticker is selected, it ruled out using that for tracking what stickers were being sent.

After doing a bit more research, I saw a few posts on StackOverflow where people were trying to add UITapGestureRecognizer’s to the MSStickerBrowserView, but even then they were having limited results. This approach would only tell you that a sticker was selected, but was unable to tell you which one.

My next attempt was to use a UICollectionView to display remotely loaded images, but it didn’t have the same look and feel as an iMessage Sticker Pack. Namely, you couldn’t click and drag the images into the iMessage transcript and the “shimmer” effect when you long pressed on a sticker was missing. However, it seemed to be the only way to incorporate analytics to see what stickers were being sent.

Playing around with this approach a little more, I was able to not only fully recreate the traditional sticker experience, but also allow the stickers to be created from remote images.

Tutorial

Let’s start off by creating a new iMessage App.

We can’t choose a “Sticker Pack App” as it offers no opportunity to write any code to fetch the images or add analytics. Instead, we’ll create an iMessage App that manages and presents a UICollectionView of MSStickerViews.

MessagesViewController Setup

First, we need to create a UICollectionView to hold the MSStickerViews.

@IBOutlet weak var stickerCollectionView: UICollectionView!

Next, we need to create an array to hold the MSSticker objects which will function as the datasource of the UICollectionView. Additionally, we’ll create an array to hold the names corresponding to the MSSticker objects displayed in the UICollectionView.

Having this list of filenames makes it easy to track and report what stickers are being sent.

//The name of the file that the image should be saved as. 
var fileName = [String]()
var stickers = [MSSticker]()

Stickers From Remote Images

MSStickerView’s require the image they display to be locally stored. So, first we’ll write code to fetch a list of remote URLs and save them locally on the device.

Next, we’ll load the images from their URLs and save the images to the DocumentsDirectory

for image in imageURLs {
    let lastPathComponent = (image as NSString).lastPathComponent
    self.fileName.append(lastPathComponent)
    self.createStickerFromRemoteImage(imageURL: image, 
fileName: lastPathComponent)
}

We need to handle the local store of .gifs and .pngs differently. The full code is available on GitHub.

Displaying And Tracking Stickers

Now we have the ability to properly fetch both .gifs and .pngs from a URL and save them to the DocumentsDirectory.

Once the image is locally saved, we use the file path to the image to create an MSSticker object. We maintain a collection, stickers, which serves as the data source to the collection view we made earlier.

Now, we get to the fun part. My final solution was to create a UICollectionView where the UICollectionViewCell’s contained an instance of a MSStickerView.

First, let’s create our new custom UICollectionViewCell

class StickerCell: UICollectionViewCell {
    @IBOutlet var stickerView: MSStickerView!
}

The first problem I ran into with my UICollectionView approach was that the UICollectionViewCell would “steal” the touch event from the underlying MSStickerView. To resolve this, simply add the following code to the project

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

Now, multiple gesture recognizer events can happen simultaneously without cancelling out the other.

Next, we use the stickers array we created earlier to populate the UICollectionView. For each cell, we populate the stickerView with the corresponding sticker from the datasource.

The call to startAnimating() starts the animation of the .gif, if applicable.

For each cell, we add a UITapGestureRecognizer with the name of the sticker it is attached to. Whenever the cell is pressed, it’s easy to track and report the sticker that was selected.

Now, you have the ability to remotely load images, present them as stickers, and track their respective popularity.

Conclusion

Eventually, you could easily create an endpoint that returns a list of image URLs. So, as seasons change, new designs are generated, etc, you can easily update your sticker collection in real-time without submitting a new release.

For the full source code, check out the GitHub repo.