Learn How to Build an iMessage App with Swift
iMessage apps are Apple’s latest attempt to fuel more conversations using iOS devices. With just a little code, you can create an extension to add your custom logic and interactions to Apple’s iMessage app. In this article, I will show you how to create a simple iMessage app that lets you rate an image with a friend in an iMessage conversation.
The Messages Framework allows you to extend iMessages to create two types of extensions: sticker packs and iMessage apps. Apple allows you to create your extension as either a standalone app or as an extension to an existing app.
Sticker packs are the easier of the two extensions to create because they require no code. A sticker pack is just an asset catalog that contains images, which users can attach to message bubbles. You can learn more about the image requirements (sizes, formats, etc) here.
iMessage apps give you access to a deeper integration with iMessage app. An iMessage extension can create a custom UI to be presented to a user within iMessages. You can also create dynamic stickers for users or insert rich media types into a conversation. iMessage apps can create interactive content or even update a message in a conversation be taking advantage of the MSMessage and MSConversation objects.
The Messages framework gives you a lot of control when creating your iMessage app. The remainder of this article will focus on how to create an iMessage app and the main classes you need to know.
Our sample iMessage app is called Ratethisnow. Our app will let you rate an image on the scale of 1–5 stars and share any addition comments you have on the image with a friend. To simplify our example for this posting, we are restricting our example to just 2 users so that we can use the iPhone Simulator.
There are a few other things that I employ in our demo to make things simpler; we will cover those as they come up.
Start Xcode 8 and create a new project, selecting the iMessage Application template under the iOS tab. Name your project ratethisnow. If you just want to follow along, you can get my sample project from Github here.
Let’s take a quick look at the project explorer. The template for an iMessage app contains an entry point (MessagesViewController.swift) and a storyboard (Maininterface.storyboard) for which we can add our custom views. Click on MessagesViewController and view the contents. Our swift class inherits from MSMessagesAppViewController and overrides several of its classes. MSMessagesAppViewController is the main class for an iMessage extension. You need to override methods in your subclass in order to interact with the iMessages. You can learn more about the methods available in the documentation here. You can also get a broader overview of app extensions in the App Extension Programming Guide.
Our app allows a user to create a rating of an item and share it in a conversation. Create a new swift class named RTNItem to serve as our model object. We need to store a caption, an image, and user ratings. As mentioned earlier, we are going to take some shortcuts to keep this article manageable. Therefore, we are going to only allow two raters, as the iPhone Simulator only contains two users that we can use for testing. You could certainly change this to a collection to suit your purposes in the future.
A user rating consists of a value from 0 to 5. When we get to the UI portion of our example, we will want an easy way to connect a rating value to a rating image, which will be represented with stars. For now, let’s create a Rating enumeration that holds a rating value and can return a rating image with the same name. Our sample project contains our images in the assets bundle.
iMessage apps need to handle two view scenarios: compact and expanded. The compact view is the initial view that is displayed when you launch your extension. The expanded view is a fullscreen view that a user can launch pressing the built-in expand button or via code programmatically. In many cases, developers use the compact view to hold dynamically generated stickers that can be appended to message bubbles. In our case, we are going to use the compact view to add a button to launch our ratings creator view. Our ratings creator view will be the expanded view and will allow a user to provide a caption and a rating.
Create a new swift file and name it RTNRatingsViewController. We will make it a subclass of UICollectionViewController. Let’s add a ratings array to hold our data for our collection view. For our scenario, we need to represent two different types of collection view items: a create button and a sticker type. To make this easier, we will create a RatingsCollectionItem enum to hold both types and a ratingsList array of the RatingsCollectionItems to serve as the datasource for our collection view.
Next we implement our required datasource delegate methods as shown below.
We still need to do a bit more work on this class to make it operational. However, we need to cover more another topic before we dive into the details. So, we will come back to our class in a bit.
Create a new swift file and name it RTNCreateRatingViewController. This class will be a subclass of UIViewController and will hold our UI elements needed to represent and rate an item. We will need to add the IBOutlets and IBAction for our view as shown below.
Since this view is where we rate our item, we need to be able to add a rating as well as publish our rating to the other iMessage user in our conversation. This means we need to add two IBAction methods to accomplish those tasks. We will create a changeRating method to cycle through the ratings and an addRating method to submit our rating.
Again, we have a little more work to do to complete this class, but we are at a good stopping point to start to connect our view controller code to real UIs in our storyboard.
Click on MainInterface.storyboard. We are going to add two view controllers and back them with the classes we just created.
Drag out a UICollectionViewController into our scene. Click the Identity Inspector in the top right of interface builder and change the custom class name to RTNRatingsViewController using the drop down. You also need to give the Storyboard ID the name RTNRatingsViewController to make it easier to find in code.
Next, we need to configure two Collection view cells: one for our create button and the other for sticker types. By default, the collection view will have one CollectionViewItem cell. We will need to add another by first selecting the Collection view object in the scene list under our RTNRatings ViewController and then switching to the Attributes inspector (located in the top right list of menu options) and incrementing the items to 2. Each CollectionViewItem needs to uniquely defined by type so we will need to add unique names for each cell. Name the first one addratingscell and the second stickerCell. You will find the identifier label by selecting each cell and then switching to the Attributes Inspector menu.
We’re almost done with this view. We just need to set the cell sizes and add an ‘add’ image for our ‘create’ cell. We can change the cell sizes by selecting the Collection View object and then switching to the Size inspector (located in the top right block of menus). Change your settings to match mine as shown in the image below.
To add our ‘add’ image, select the ‘add’ collection view cell and drag out an UIImageView to cover the cell. Choose the addrating image from our asset catalog.
Drag out a UIViewController into the scene. Click the Identity Inspector in the top right of interface builder and change the custom class name to RTNCreateRatingViewController using the drop down. You also need to give the Storyboard ID the name RTNCreateRatingViewController.
This is just a simple view. You need three buttons and a textfield. Arrange the view to match mine as shown in the image below. If you have trouble with your layout, you can refer to the completed project files that you can find here.
Once you have completed the layout, you can wire up the outlets contained in the code file. At this point, you might be wondering why the rated item is a button and not an image. The answer is that I took a simplifying shortcut to keep the article short and too keep the attention on iMessage interaction. With a little imagination and code, you could wire this button action to launch a view with a list of images for which the user could select to base a rating on. Another angle could be that this action launches the camera to allow the user to snap a photo of something to rate. I’ll leave that to you to explore later.
With our UIs complete, let’s switch to the MessagesViewController and cover the logic contained that governs our iMessage interactions. When our iMessage extension needs to be presented, it is up to us to tell the extension which UI to present and to handle both compact and full screen views. I have centralized the work of presenting the appropriate UI and size to the presentRatingsViewController method. This method checks the presentation style and displays the RTNRatingsViewController view for compact style and RTNCreateRatingViewController view for the expanded style. We then call this method when our extension changes from inactive to active state (willBecomeActive method) and when our presentation style changes (willTransition method).
Earlier in this article I mentioned that we would need to come back to finish adding some details to get our app to work properly. Let’s do that now and start with our RTNRatingsViewController. When a user presses the add rating collection view cell, we need to notify our MessagesViewController that we want to change our presentation style from compact to expanded, and we will use the delegate pattern to facilitate this. Add the following lines to RTNRatingsViewController to create a protocol that MessagesViewController will implement.
Next, override the didSelect view delegate method to trigger our didSelectRatingsItem method if the add rating cell has been selected.
Finally, switch back to the MessagesViewController and add an extension method that implements the didSelectRatingsItem of the RTNRatingsViewControllerDelegate. Here we will request a presentation style change to the expanded view.
With these changes, we can now select the add button that is presented in the compact view and have our display update to the expanded view.
The other change we need to make involves adding ratings that are sent from one user to the other. Once we have completed a rating in our RTNCreateRatingViewController view, we need to let our MessagesViewController know that we are ready to send our message. Again, we will use a delegate for communication. First, we create the RTNCreateRatingViewControllerDelegate protocol with an addRatingForItem method. After that, we add a delegate variable and call the delegate’s method from the addRating method that is triggered when a user presses the Rate it! button.
In MessagesViewController, we add an extension that implements the RTNCreateRatingViewControllerDelegate protocol’s addRatingForItem method to obtain the current conversation, compose a new message, and add to conversation; triggering the message to be sent.
We handle composing the new message in the composeMessage function.
A MSMessage holds all the data it needs to transfer a message to a recipient’s device. This object knows the message session it belongs to as well as all of the recipients in the conversation. The MSMessage’s url property is particularly important because it is used to encode all data sent in the message. The current best practice for encoding your data is to use to create URLQueryItems for each field you want to include with your message. If we now look at the composeMessage function, we can see that I have created URLQueryItems for our caption and ratings.
A MSMessage object also has a graphical representation that you can customize and send with your message. We can manipulate this representation using a MSMessageTemplateLayout, which gives us access to the properties.
In the composeMessage function, I set the base image for the template. In your project, you could add a caption or set other fields that make sense for your uses.
For many applications, it makes sense to know who the users are in the conversation. However, Apple does not give your code access to the users directly. Instead, you have access to the UUIDs for each recipient in the conversation. You can use the syntax below to display a user’s name the text locations of the MSMessageTemplateLayout.
Unfortunately, Apple only allows you to use this feature when displaying a user’s name in a message layout. If you want to know if the sender is the one on the current device, you can use the a property on the session to test for equality
Once we have our completed MSMessage, we insert it into the active conversation and it is transferred to the remote recipients.
Let’s test our work and see how things work in a demo. Apple provides a way for us to test iMessage apps using the simulator. For iMessage apps, we are given two users that that can send and receive messages from the other user. Let’s run our iMessage extension and test that we can create a rating and see the created message with both users.
That concludes our project, but it doesn’t have to end here. I created a simplified project to show you how to get started. A good exercise would be to see if you can extend this project to make it usable by more than two users and to find interesting things to let your users rate. If you haven’t already, you can find a link to the code for this project here.
I hope this article has been useful to you. I think that iMessage apps do have promise, and we only touched the surface in terms of what’s possible!
On a lighter note, I also wrote an article on why creating native apps is probably the best way to go in most of your mobile development endeavors. Read it and weigh in the discussion!
If you find this post helpful, please recommend it for others to read. If you’re looking to learn more about what’s new in Swift 3, check out my new book Swift 3 New Features. Thanks!