Menu
Martin Hagerup

This fall we released our first standalone iMessage app, Snakk. Making it easy to find relevant shareable content to send to your friends. Not just images, but sound clips too!

I want to sum up some of the experiences I gained while working with this bleeding edge Messages Framework.

Bread and butter, view-handling

MessagesAppViewController will work as the entrypoint of the application, as well as the main working horse.
It quickly became clear that the traditional navigation-stack based on UINavigationController with segues and all that had some strange side-effects. It would seem these apps aren't meant to built, or look like regular apps. We then turned to using one main view controller, and manually changing the presenting view. Our bread and butter for this became something in the lines of:

func present(viewController controller: UIViewController) {    
    // Remove any existing child controllers.
    for child in childViewControllers {
        child.willMoveToParentViewController(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.constraintEqualToAnchor(view.leftAnchor).active = true
    controller.view.rightAnchor.constraintEqualToAnchor(view.rightAnchor).active = true
    controller.view.topAnchor.constraintEqualToAnchor(view.topAnchor).active = true
    controller.view.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
    
    controller.didMoveToParentViewController(self)
}

Every time the user navigates, we remove the current view, and replace it with a newly configured one.

Custom transition animations

Not being able to use the navigation stack presented some issues when we wanted custom collection view transitions when the user previewed an item. Normally we would use a transitioningManager while presenting the target view controller modally. This however would not work. We could present modally, but whatever we did, the transitioningManager would never be called.

We solved this by manually doing the animation.

Please watch a demo of the transition here

func willPreviewItem(with imageURL: NSURL, thumbnail: UIImage, origin: CGRect) {
        guard let controller = storyboard?.instantiateViewControllerWithIdentifier("PreviewViewController") as? PreviewViewController else { return }
        //1
        controller.thumb = thumbnail
        controller.imageUrl = imageURL
        controller.view.frame = view.bounds
        controller.view.alpha = 0.0
        
        //2
        self.addChildViewController(controller)
        self.view.addSubview(controller.view)
        controller.didMoveToParentViewController(self)
        
        //3
        let containerView = UIImageView(frame: origin)
        view.addSubview(containerView)
        containerView.image = thumbnail
        
        UIView.animateWithDuration(0.2, animations: {
            //4
            let rect = //Size of the image view in target viewController.
            containerView.frame = rect
        }) { (finished) in
        	//5
            containerView.removeFromSuperview()
            controller.view.alpha = 1.0
        }
    }
  1. First load and configure the target view controller. Set initial alpha to 0.0, so it stays hidden.
  2. Add target view controller to current presenting view controller.
  3. Create a container view that will handle the animation. Here we provide the thumb as a placeholder image, and use that in the animation.
  4. Perform the animation in with we animate the container view to the desired target size. You might experience issues with accessing the target view controller's imageview frame, so it is advised to do the calculations manually.
  5. When animation completes, we remove the container view, and again show the target viewcontroller.

Can an iMessage Extensions look at the messages in the conversation?

The #images stock app from Apple is able to hook into the active conversation and extract possible search suggesions from the context.

Working with an iMessage extension, the MSMessagesAppViewController acts as the entry point.

  • func didReceive(MSMessage, conversation: MSConversation) is a delegate method that gets called whenever the extension is active. Only messages that come from your extension are allowed to call this method, so no snooping around the conversation here.
  • var activeConversation: MSConversation? will be set whenever the user taps a message coming from your extension.


From the documentation of MSConversation
"The MSConversation class represents a conversation in the Messages app. Use conversation objects to access information about the currently selected message or the conversation participants, or to send text, stickers, attachments, or message objects."


From this we can take that you are only able to see messages that originates from your extension.
It is highly understandable Apple wont allow third party extensions to snoop in on a conversation, as it would have been a huge mess in terms of privacy. I do however wish there would be some ways of extracting some anonymous bits of data, like mood, and very high level context awareness of the conversation for developers to use.

How to insert image into conversation from iMessage Extension without MSMessageTemplateLayout

MSConversation provides several insert methods for adding content to the transcript. An image will work as an attachment, so we need to look into func insertAttachment(URL, withAlternateFilename: String?, completionHandler: ((NSError?) -> Void)? = nil)

The URL argument need to be a file url. Meaning the file will have to exist in the local file system to be able to insert it in the transcript.
We can do this very simple by downloading the original URL, and extracting the target path.

URLSession.shared.dataTask(with: url) { (data, response, error) in
	guard error == nil, let fileName = response?.suggestedFilename else { return }
    if let writePath = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(fileName).jpg") {
       do {
       	try data?.write(to: writePath)
       } catch {
       	//handle error
       }
       self.activeConversation?.insertAttachment(writePath, withAlternateFilename: nil, completionHandler: nil)
    }
}.resume()

Now, instead of being embedded within the MSMessageTemplateLayout, the image is shared directly and tapping the message will present the native image preview.

The users can long-press the image and see the source extension that provided this image. Instead of launching the app, it will take them to the extensions app store.

Caveats

  • OpenURL is not possible, so whenever you have to present content from a web page, you will have to embed a webview to present this.
  • UIAlertController bounds does not respect the messages view controller bottom and top layout guide. Causing it to clip out of bounds under the messages view UI. We solved this by implementing our own prompt/alert/message views we can fully control.

State of the iMessage App store

Currently, the only way to succeed at the iMessage App store, is to either be featured by Apple, or bundling your extension with an already popular app. The discoverability is low, and only a few select apps/sticker packs is even listed in the different categories within the store.

Users don't know what to expect

Regular users that find the app in the normal App Store, find it interesting and decide to install it. It then says installed, but where is it? The majority of feedback we get now, is that users cannot find the iMessage App. They don't know what it is, and there is no reference to it anywhere unless you know where to find it.

You would have to open iMessage -> press that new App Store icon -> open store -> go to administration tab -> activate app
Is it our responsibility as iMessage extension developers to convey to our users how to use the core features in the operating system they use? I certainly think there is room for better onboarding towards iMessage apps within iOS/App Store.