App Development

iMessage Apps: Part One

blog-featured-image imessage-apps-part-1 RG-510x296

WWDC brought a host of cool updates to Apple products, one of which is a complete revamp of the messages app on iOS. Both Android and iOS have made messaging a priority this year, with Google announcing Allo and Apple showing off their new features in Messages: rich links, inline video, text effects, drawings, and most importantly, apps!

Apple unveiled two new ways to create apps for iMessage, sticker packs and iMessage apps. Sticker packs contain, you guessed it, stickers! A new feature of iMessage is the ability to attach custom stickers to messages. You can attach as many stickers as you want and we’ve been having a blast sending them to each other around the office. All it requires is one person to have the sticker pack for everyone in the conversation to be able to see them. Sticker packs are fairly straightforward, requiring only a set of images meeting some simple criteria. They need to be either PNG, GIF, or JPEG files, smaller than 500KB and @3x images no smaller than 100x100 and no larger than 206x206. Other than these simple restrictions, creating a sticker pack is incredibly simple, requiring no code at all to make. For fun, I put together ChibiWatch, a collection of stickers of characters from Blizzard’s new game, Overwatch. The whole process from concept to completion took just a few minutes. blog-post-image 1 iMessage-Apps RG

One unexpected bit of the iMessage apps, in general, is the app icon size. iMessage apps are not square, but rectangles with a 4:3 aspect ratio. Apple masks the corners giving you iMessage app icons a pill-shaped look. Designers should be aware of this unique trait to be ready for iMessage app icons. blog-post-image 2 iMessage-Apps RG

iMessage applications can be standalone or an extension to your existing app. iMessage will also helpfully put a notification under any messages sent with your app to users who don’t have it, providing an easy link where the user can obtain your app with a single tap.

Getting started with iMessage apps is very easy. You can obtain the necessary framework with a simple import Messages. Your primary view controller should also inherit from MSMessagesAppViewController, as this will be your entrance point to receiving events when the user opens your extension. When your extension should load, you’ll get a call to willBecomeActive(with conversation: MSConversation) that will pass you the user’s conversation. The conversation will include obfuscated UUIDs of the local and remote participants, no identifying information. The Messages framework includes some fascinating functionality and I suggest you check out all the features, we’ll run through just a few that I find really interesting.

Here’s a really simple walk-through of a message app that uses the WillowTree Name Game API to dynamically create stickers of WillowTree folks that I can then use to spam friends. I’ll start with my willBecomeActive function to hook into the extension launch.

override func willBecomeActive(with conversation: MSConversation) { 
    super.willBecomeActive(with: conversation)
    // Use this method to configure the extension and restore previously 
stored state.
    presentViewController(with: presentationStyle)
}


private func presentViewController(with presentationStyle: 
MSMessagesAppPresentationStyle) {


    let controller: UIViewController
    if presentationStyle == .compact {
        controller = WillowTreeCollectionViewController.build(nameService: 
nameService, delegate: self)
    } else {
        // todo: have different functionality for compact vs expanded view
        controller = WillowTreeCollectionViewController.build(nameService: 
nameService, delegate: self)
    }


    // Remove any existing child controllers.
    for child in childViewControllers {
        child.willMove(toParentViewController: nil)
        child.view.removeFromSuperview()
        child.removeFromParentViewController()
    }


    // Embed the new controller.
    addChildViewController(controller)


    controller.view.frame = view.bounds
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(controller.view)


    controller.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = 
true
    controller.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive 
= true
    controller.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = 
true
    controller.view.bottomAnchor.constraint(equalTo: 
view.bottomAnchor).isActive = true


    controller.didMove(toParentViewController: self)
}

Notice that we’re checking the member presentationStyle. iMessage app extensions support two different styles, compact and expanded. The compact presentation style is shown where the user’s keyboard is typically displayed, as shown in the first screenshot. The expanded presentation style takes up the entire messaging window with only the text field remaining below. Your app should be adaptive and respond to both of these presentation styles, as the user may transition between them at any time. You’ll receive a callback to willTransition(to presentationStyle: MSMessagesAppPresentationStyle) when this happens so you can prepare your view controller. Currently, I’m just displaying the same view controller for both styles, we’ll come back to this later as we add functionality.

I’ll be pulling my images from our Name Game API, so I create a simple NameService protocol with the following signature.

protocol NameService {
    func people(completion: ((Result<People, ErrorType>) -> Void))
}

Where People is a typealias for an array of simple structs:

struct Person {
    var name: String
    var imageURL: URL
}


typealias People = [Person]

This way I can easily Mock the service for testing. Now the API will supply us with images in a .PNG format, but we’ll need to convert these to use in MSStickerView’s if we want them to behave like the static stickers above. For our collection view of stickers, we’ll make each cell contain a simple view that we specify as an MSStickerView.

blog-post-image 3 iMessage-Apps RG

Now we set up our PersonCollectionViewCell like so:

class PersonCollectionViewCell: UICollectionViewCell {


    @IBOutlet var personSticker: MSStickerView!
    let cache = FaceStickerCache.cache


    override func prepareForReuse() {
        super.prepareForReuse()
        personSticker.sticker = cache.placeholderSticker
    }


    func configure(with person: Person) {


        // Note: when creating a sticker from a network request, you currently MUST use a placeholder,
        // failing to use one will result in a cryptic crash when the sticker attempts to display
        personSticker.sticker = cache.placeholderSticker
        cache.sticker(for: person) { [weak self] (sticker) in
            DispatchQueue.main.async {


                self?.personSticker.sticker = sticker //stickler for stickers
                self?.personSticker.sizeToFit()
            }
        }
    }
}

First, we setup our personSticker with a placeholder. If you’re making a sticker asynchronously, be sure to provide a placeholder - this tripped me up for a bit when I was starting out. Next, we use our Person we fetched from the API and pass that to our FaceStickerCache to create and return our MSSticker from the image URL we were given from the API. Once that returns our sticker we update and resize to fit within the cell.

The FaceStickerCache creates a temporary directory on disk and reads the image from the URL and writes it to a file locally, which we can then reuse. There’s a link to the project here where you can take a look at everything it’s doing, but let’s check out the interesting bit, getting the sticker.

func sticker(for person: Person, completion: (sticker: MSSticker) -> Void) {
// Determine the URL for the sticker. Spaces were causing issues so replace with _
let fileName = person.name.replacingOccurrences(of: " ", with: "_") + ".png"
guard let url = try? cacheURL.appendingPathComponent(fileName) else { 
fatalError("Unable to create sticker URL") }

// Create an operation to process the request.
let operation = BlockOperation {
    let fileManager = FileManager.default()

    // return early if we have the file already
    guard let path = url.path where !fileManager.fileExists(atPath: path) else { return }

    // Create the sticker image and write it to disk.
    let data: Data
    do {
        data = try Data(contentsOf: person.imageURL)
    } catch {
        fatalError("Can't obtain contents of url")
    }
    guard
        let image = UIImage(data: data),
        let imageData = UIImagePNGRepresentation(image) else { fatalError("Unable to create image") }

    do {
        try imageData.write(to: url, options: [.atomicWrite])
    } catch {
        fatalError("Failed to write sticker image to cache: \(error)")
    }
}

// Set the operation's completion block to call the request's completion handler.
operation.completionBlock = {
    do {
        let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: person.name)
        completion(sticker: sticker)
    } catch {
        print("Failed to write image to cache, error: \(error)")
    }
}
// Add the operation to the queue to start the work.
self.queue.addOperation(operation)}

We create a background operation where we can fetch the image from the URL if we need to and write it to disk without blocking the UI, then we add a completion that uses that local file to make our MSSticker object and call our completion to return it. Let’s take a look at a quick example of what we have so far in action.

blog-post-image 4 iMessage-Apps RG

XCode 8 includes some helpful tools for writing iMessage apps, notice the two simulated users in iMessage that let me send stickers back and forth to test functionality.

One of the coolest things with the new iMessage is the ability to collaboratively create and update messages. Each MSConversation object may return a selectedMessage property that you can pass back and forth between the recipients. You can keep track of the message object to modify an already-sent message and provide notifications without cluttering the conversation with new messages. The selected message can be modified by each participant and sent back to the rest of the group, where you can keep everyone updated on in-progress messages by responding to didReceive(_ message: MSMessage, conversation: MSConversation) calls.

What use cases canyou think of for iMessage apps with collaborative messaging? Stay tuned for part two where I’ll extend the current app to work with this functionality. In the meantime, check out Apple’s Ice Cream Builder to get an idea of what’s possible.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article