The sticker app you created before doesn't contain a lot of user interface elements. Your app shows stickers on a white background and that's about it. Sticker apps shouldn't be more complex than this, but wouldn't it be great if we could at least change the background color for our sticker app? This isn't possible if you're using the simple sticker pack template. You can, however, create your own sticker pack app and customize the background. Figuring out how to do this will allow you to familiarize yourself with the code that's involved in creating an iMessage app, so let's create a custom sticker pack.
In the source code repository for this book, you'll find a project named CustomStickers. This project already contains the stickers we're going to display. The images for these stickers are made available through openclipart.org by a user named bocian. Note that the images have not been added to the Assets.xcassets folder, but to the Stickers folder in the MessagesExtension. The project was set up like this because the Assets.xcassets folder belongs to the containing app that we don't have.
In the MessagesExtension folder, you'll find a view controller file and a storyboard file. Open the storyboard and remove the default UILabel that was placed in the interface. We're not going to add any interface elements through Interface Builder because our interface elements aren't directly available in Interface Builder.
In the MessagesViewController, you'll find several boilerplate methods. We'll get into them soon; you can ignore them for now. We're going to use viewDidLoad to set up a MSStickerBrowserViewController to display stickers in. The MSStickerBrowserView instance that is contained inside the MSStickerBrowserViewController behaves somewhat like a UITableView does because it requires a data source to determine how many and which stickers to display.
The first step in implementing our own sticker app is to add a property for an instance of MSStickerBrowserViewController to MessagesViewController:
var stickerBrowser = MSStickerBrowserViewController(stickerSize: .regular)
Next, add the following implementation for viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
stickerBrowser.willMove(toParentViewController: self)
addChildViewController(stickerBrowser)
stickerBrowser.didMove(toParentViewController: self)
view.addSubview(stickerBrowser.view)
stickerBrowser.stickerBrowserView.dataSource = self
stickerBrowser.stickerBrowserView.reloadData()
stickerBrowser.stickerBrowserView.backgroundColor = UIColor.red
}
This snippet should not contain any surprises for you. First, the stickerBrowser is added as a child view controller of the messages view controller. Then we add the view of stickerBrowser as a subview of the messages view controller's view. Next, we set a dataSource on the stickerBrowserView and we tell it to reload its data. And finally, we set the background color for the stickerBrowserView; the whole reason why we implemented a custom sticker app. Again, nothing strange or surprising.
If you build your app now, Xcode will complain about the fact that MessagesViewController does not conform to MSStickerBrowserViewDataSource. Add this protocol to the declaration of MSMessagesViewController and implement the following two methods to conform to MSStickerBrowserViewDataSource:
func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return OwlStickerFactory.sticker(forIndex: index)
}
func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return OwlStickerFactory.numberOfStickers
}
The first method is expected to return a sticker for a certain index and the second returns the number of stickers in our app. The logic for this has been abstracted into a sticker factory. This is done in order to keep the code in the view controller nice, compact, and to the point. Add a Swift file to the project and name it OwlStickerFactory. Add the following implementation to this file:
import Foundation
import Messages
struct OwlStickerFactory {
static private let stickerNames = [
"bike", "books", "bowler", "drunk", "ebook",
"family", "grill", "normal", "notebook", "party",
"punk", "rose", "santa", "spring"
]
static var numberOfStickers: Int { return stickerNames.count }
static func sticker(forIndex index: Int) -> MSSticker {
let stickerName = stickerNames[index]
guard let stickerPath = Bundle.main.path(forResource: stickerName, ofType: "PNG")
else { fatalError("Missing sicker with name: \(stickerName)") }
let stickerUrl = URL(fileURLWithPath: stickerPath)
guard let sticker = try? MSSticker(contentsOfFileURL: stickerUrl, localizedDescription: "\(stickerName) owl")
else { fatalError("Failed to retrieve sticker: \(stickerName)") }
return sticker
}
}
Most of this code should speak for itself. There is an array of sticker names and a computed variable that returns the number of stickers for the app. The interesting part of this code is the sticker(forIndex:) method. This method retrieves a sticker name from our array of names. Then it retrieves the file path that can be used to retrieve the image file from the application bundle. Finally, it creates a URL with this path in order to create a sticker.
Note that the MSSticker initializer can throw errors so we prefix our initialization call with try? since we're not interested in handling the error. Also, note that the sticker initializer takes a localizedDescription. This description is used by screen readers to read back the sticker to users that have certain accessibility features enabled.
Any time we add an MSSticker to the view, regardless of whether we're using a sticker browser or not, the Messages framework takes care of the sticker peel, drag, and drop actions for us. This means that you can create a completely different, custom interface for your stickers if you'd like. Keep in mind that most sticker apps will make use of the standard layout and your users might not be too pleased if your app presents them with an unexpected sticker sheet layout.
However, apps that aren't about stickers do require a special layer of design and interaction. This is the next topic we'll cover.