Adding instructional coach marks to iOS app with Swift
Nov 2, 2015
I generally don’t like onboarding screens, which no one actually reads, and they just stand in the way of using the app. User already found, downloaded and launched your app, why would make 4 screen of obstacles to your app you spent so much time developing?
What I like is the idea of teaching the user about non obvious features of the app to make him productive in context of application. And user should be able to clearly see the Skip button to just explore on his one.
With that in mind I chose Instructions Swift library that shows cute coach marks and has in my view handy architecture inside.
Adapting Instructions to support iOS 7.0
We have about 12% of users on iOS 7 and since Instructions was built with iOS 8 in mind, it means 12% users won’t be able to enjoy new features, and we will probably lose their business. I decided to refator Instructions to support iOS 7 and upwards.
Main obstactle for iOS 7 support is usage of UIBlurEffectStyle and UIVisualEffectView to blur background while showing coach mark.
We’ll use darkened background instead of blur on iOS 7 and blur on iOS 8 and up. I declared attributes specifying UIBlurEffectStyle and UIVisualEffectView as AnyObject?, on assignment will convert enum value to NSNumber and will cast them to enum depending on iOS version. Swift has very handy iOS version checking @available(iOS 8.0, *) which I’ll use to mark parts of code as unavailable for iOS 7.
Next, Instructions also uses UIImage.imageNamed:inBundle:compatibleWithTraitCollection: initilizer available only on iOS 8 and up.
I refactored by moving initialization to default initializer and loading UIImage from file path for images in current bundle:
I did the same for CoachMarkArrowDefaultView.swift:
Second problem is Instructions is a Swift dynamic library supported only after iOS 8. I tried to make static, but then Cocoapods complained that I can’t use Swift static library.
I have to copy Instructions code and assets into my project and probably manually merge future library versions until we drop iOS 7.0 support.
Adding Store controller coach marks
I want to show non standard UI component used for quick navigation to departments and aisles. I decided to follow so called Protocol Oriented Development and instead of adding new code to existing view controllers create Swift extensions.
First, I created generic UIViewController extension to add coach marks capability to every UIViewController descendant. It stores CoachMarksController instance in associated object, setups CoachMarksController, and Skip control to skip instructions all together:
Next, I want to highlight portions of the UI on iPhone 6s/6s Plus that user can 3D Touch to preview departments and products without leaving main store screen:
Finally, I call setupCoachNarks in viewDidLoad and startCoaching after store infromation has been downloaded from server and collection view was populated:
Here is how it looks with everything integrated:
Department coach marks
Recently I ditched drop down menu for navigation in departments and aisles and now use custom UIPageControl with scrolling list of all departments to allow user to swipe between controllers. I want to highlight that:
I show coach mark as soon as view appears, but viewDidAppear gets called during 3D Touch Peek and gets really weird. I added property to disable coach mark during 3D Touch Peek.
Deleting and adding notes to order items
Some people can’t figure out how to delete order item from their cart. I’ll show instruction one time after they visit their cart with at least 1 order item.
I’m using custom coach mark CoachMarkBodyView implementation with colorful button to be consistent with the rest of UI.
Cart & Search Instructions
Don’t annoy user
Let’s be nice to our users and don’t annoy them with repetitive instructions. If user skips instructions or seen at least one coach mark for the given view controller, we will not show instructions next time.
I created InstructionScreens enum and default initializer to map view controllers to enum values:
Then, I write and read NSUserDefaults to track which screen to coach user about or not:
Last, in startCoaching I check if current view controller coaching was completed and save completion in didFinishShowingFromCoachMarksController, don’t forget to set coachMarksViewController.delegate:
I’m genuinely impressed with Swift enums, they make code so expressive and readable!