This presentation shows three different ways of creating iMessage sticker apps - the first one is for beginners and the third one is the most complex with lots of possibilities for customization.
1. A short overview of different
types of iMessage Sticker
Apps
November, 2016
2. www.byteout.com
Three levels of customization
1. Simple Sticker App
○ Set sticker size
2. Customized Sticker App
○ Set background color/image
○ Add elements (views, links, texts) behind or on top of stickers
3. Totally Custom Sticker App
○ Different size of each sticker
○ Custom layouts
○ Interaction
○ Mix up stickers with other elements (links, buttons, views, ...)
9. www.byteout.com
2. More customized Sticker App
1. Create iMessage app
2. Subclass MSStickerBrowserViewController and
a. Load stickers
b. Implement numberOfStickers(in:) and stickerBrowserView(_:stickerAt:)
3. Make simple customizations
a. Change background color (Storyboard/code)
b. Add button/text/link on top of or behind the sticker browser view
4. Load MSStickerBrowserViewController into
MessageViewController
12. 2.2. Loading stickers and implementing required methods
var stickers = [MSSticker]()
func loadStickers() {
let names = ["heart", "romb", "club", "star", "square", "circle"]
for name in names {
if let url = Bundle.main.url(forResource: name, withExtension: "png") {
do {
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "")
stickers.append(sticker)
} catch {
print(error)
}
}
}
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int)
-> MSSticker {
return stickers[index]
}
13. 2.5. Loading StickerBrowserViewController into MessagesViewController: step 1 - add Container View into
MessagesViewCtontroller and set up the constraints, background colors, etc.
14. 2.5. Loading StickerBrowserViewController into MessagesViewController: step 2 - set the class of newly created View Controller to
be StickerBrowserViewController (name of your custom class)
15. Preview of the app - background color of container view was changed
17. www.byteout.com
3. Totally custom Sticker App
1. Create iMessage app
2. Subclass UIViewController and add subviews
a. Views, buttons, labels, … (optional)
b. UICollectionView (required) and implement its dataSource methods:
i. numberOfSections(in:)
ii. collectionView(_:numberOfItemsInSection:)
iii. collectionView(_:cellForItemAt:)
3. Subclass UICollectionViewCell and add MSStickerView to it
(possible through Storyboard)
a. Load sticker into MSStickerView in collectionView(_:cellForItemAt:)
method
4. Load UIViewController into MessageViewController
25. 3.3. Change collectionView(_:cellForItemAt:) function so it can load sticker in the cell (in MyCustomStickerViewController.swift)
// Array of stickers
var stickers = [MSSticker]()
// Cell for item at index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->
UICollectionViewCell {
// Load reusable cell using its identifier;
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for:
indexPath) as! StickerCell
// Set up cell with its sticker
cell.stickerView.sticker = stickers[indexPath.row]
return cell
}
26. 3.3. Helper methods for loading stickers (MyCustomStickerViewController.swift)
// Create sticker
func createSticker(asset: String, localizedDescription: String) {
// Get path for asset
guard let stickerPath = Bundle.main.path(forResource: asset, ofType:"png") else {
print("couldn't create the sticker for asset ", asset)
return
}
// Create url from the path
let stickerUrl = URL(fileURLWithPath: stickerPath)
// Load asset into sticker and append the array of stickers with the new element
let sticker: MSSticker
do {
try sticker = MSSticker(contentsOfFileURL: stickerUrl,
localizedDescription:localizedDescription)
stickers.append(sticker)
}
catch {
print(error)
return
}
}
27. 3.3. Helper methods for loading stickers (MyCustomStickerViewController.swift)
// Load all assets and create stickers from them
// This can be done in any way - through a loop, dictionary of names and localized descriptions,
etc.
func loadStickers() {
createSticker(asset: "heart", localizedDescription: NSLocalizedString("heart", comment: ""))
createSticker(asset: "romb", localizedDescription: NSLocalizedString("romb", comment: ""))
createSticker(asset: "club", localizedDescription: NSLocalizedString("club", comment: ""))
createSticker(asset: "square", localizedDescription: NSLocalizedString("square", comment:
""))
createSticker(asset: "star", localizedDescription: NSLocalizedString("star", comment: ""))
createSticker(asset: "circle", localizedDescription: NSLocalizedString("circle", comment:
""))
}
28. 3.4. Load MyCustomStickerViewController in MessagesViewController.swift
private let StickersViewController = "StickersViewController"
override func viewDidLoad() {
super.viewDidLoad()
let _ = loadViewController(StickersViewController)
}
// Load a view controller with given identifier
func loadViewController(_ viewControllerIdentifier: String) -> Bool {
// Remove any existing child controllers.
childViewControllers.forEach {
$0.willMove(toParentViewController: nil)
$0.view.removeFromSuperview()
$0.removeFromParentViewController()
}
// Instantiate view controller from storyboard, using its identifier...
guard let vc = storyboard?.instantiateViewController(withIdentifier: viewControllerIdentifier)
else {return false}
vc.addTo(appViewController: self)
return true
}
29. 3.4. UIViewController extension - for adding a view controller to any container
// Extension for UIViewController class - adds additional functions to a class
extension UIViewController {
// Load any view controller into Messages App
func addTo(appViewController host:MSMessagesAppViewController) {
// Add child view controller
willMove(toParentViewController: host)
host.addChildViewController(self)
// Set up the frame of child view controller
view.frame = host.view.bounds
view.translatesAutoresizingMaskIntoConstraints= false
host.view.addSubview(view)
/*!
@discussion The NSLayoutAnchor class is a factory class for creating NSLayoutConstraint objects using
a fluent API. Use these constraints to programatically define your layout using Auto Layout.
*/
view.leftAnchor.constraint(equalTo: host.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: host.view.rightAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: host.view.bottomAnchor).isActive = true
// We do not want our view controller to be covered with the contact name, that is why this one is different
view.topAnchor.constraint(equalTo: host.topLayoutGuide.bottomAnchor).isActive = true
didMove(toParentViewController: host)
}
}
30. www.byteout.com
3.a. Customize Collection View Layout
If you do not like the grid layout, you can create your own.
1. Subclass UICollectionViewLayout and override methods:
a. prepare()
b. collectionViewContentSize (getter function)
c. layoutAttributesForElements(in:)
2. Set UICollectionView to use this custom layout class
32. 3.a. Implement layout methods: Calculate positions of every sticker in prepare() method. Cache layout attributes for performance.
(in StickerLayout.swift)
override func prepare() {
if cache.isEmpty {
for i in 0 ..< collectionView!.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: i, section: 0)
// Create new attributes for cell with index path
let attributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
// First row has two columns, second three, third two again...
if ((row % 2) == 0) {
col = 2
}
else {
col = 3
}
// Calculate sticker width based on number of columns
width = Int((Double(contentWidth) - Double((col + 1) * cellPadding)) / Double(col))
attributes.frame = CGRect(x:(colCounter * width + (colCounter + 1) * cellPadding), y:(row * height + (row +
1) * cellPadding), width:width, height:height)
cache.append(attributes)
colCounter += 1
// If we have filled a row, we proceed to another row
if (colCounter == col) {
row += 1
colCounter = 0
}
}
}
}
33. 3.a. Implement layout methods: defined properties and collectionViewContentSize for defining scroll view content size (in
StickerLayout.swift)
private let cellPadding = 6 // Space between sticker cells
private let height = 100 // Height of a sticker - equal for all of them
private var row = 0 // Total number of rows
private var col = 2 // Number of columns in a row
private var colCounter = 0 // Current column
private var width = 1 // Sticker width
private var cache = [UICollectionViewLayoutAttributes ]() // Cached attributes
private var contentWidth: CGFloat {
// Returns width of the collection view
return collectionView !.bounds.width
}
// CollectionView content size
override var collectionViewContentSize: CGSize {
if (colCounter != 0) {
return CGSize(width: contentWidth , height: CGFloat((row + 1) * height + (row + 2) *
cellPadding ))
}
else {
// If the last row is completely full, variable row is already increased by one
return CGSize(width: contentWidth , height: CGFloat(row * height + (row + 1) * cellPadding ))
}
}
34. 3.a. Implement layout methods (in StickerLayout.swift): layoutAttributesForElements(in:)
// Layout attributes for elements in rect - to return layout attributes for for supplementary or
decoration views, or to perform layout in an as-needed-on-screen fashion.
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
{
var layoutAttributes = [UICollectionViewLayoutAttributes]()
// Get attributes from cache...
for attributes in cache {
if attributes.frame.intersects(rect) {
layoutAttributes.append(attributes)
}
}
// ... and return them
return layoutAttributes
}