Loading repository data…
Loading repository data…
Rakuten-MTSD-PAIS / repository
Create walkthroughs and guided tours (coach marks) in a simple way, with Swift.

Add customizable coach marks into your iOS project. Available for both iPhone and iPad.
UIVisualEffectView supportIf you need help with something in particular, ask a question in the Gitter room.
If you want to contribute, be sure to take a look at the contributing guide.
⚠️ Git LFS is required to clone Instructions or build it through your favorite package manager (all the snapshots used in the tests are stored in a submodule through LFS).
Add Instructions to your Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
pod 'Instructions', '~> 1.3.1'
Then, run the following command:
$ pod install
Add Instructions to your Cartfile:
github "ephread/Instructions" ~> 1.3.1
You can then update, build and drag the generated framework into your project:
$ carthage update
$ carthage build
If you rather stay away from both CocoaPods and Carthage, you can also install Instructions manually, with the cost of managing updates yourself.
Open up the controller for which you wish to display coach marks and instantiate a new CoachMarksController. You should also provide a dataSource, which is an object conforming to the CoachMarksControllerDataSource protocol.
class DefaultViewController: UIViewController, CoachMarksControllerDataSource, CoachMarksControllerDelegate {
let coachMarksController = CoachMarksController()
override func viewDidLoad() {
super.viewDidLoad()
self.coachMarksController.dataSource = self
}
}
CoachMarksControllerDataSource declares three mandatory methods.
The first one asks for the number of coach marks to display. Let's pretend that you want to display only one coach mark. Note that the CoachMarksController requesting the information is supplied, allowing you to supply data for multiple CoachMarksController, within a single dataSource.
func numberOfCoachMarks(for coachMarksController: CoachMarksController) -> Int {
return 1
}
The second one asks for metadata. This allows you to customize how a coach mark will position and appear, but won't let you define its look (more on this later). Metadata are packaged in a struct named CoachMark. Note the parameter coachMarkAt, it gives you the coach mark logical position, much like and IndexPath would do. coachMarksController provides you with an easy way to create a default CoachMark object, from a given view.
let pointOfInterest = UIView()
func coachMarksController(_ coachMarksController: CoachMarksController,
coachMarkAt index: Int) -> CoachMark {
return coachMarksController.helper.makeCoachMark(for: pointOfInterest)
}
The third one supplies two views (much like cellForRowAtIndexPath) in the form a Tuple. The body view is mandatory, as it's the core of the coach mark. The arrow view is optional.
But for now, lets just return the default views provided by Instructions.
func coachMarksController(_ coachMarksController: CoachMarksController, coachMarkViewsAt index: Int, madeFrom coachMark: CoachMark) -> (bodyView: CoachMarkBodyView, arrowView: CoachMarkArrowView?) {
let coachViews = coachMarksController.helper.makeDefaultCoachViews(withArrow: true, arrowOrientation: coachMark.arrowOrientation)
coachViews.bodyView.hintLabel.text = "Hello! I'm a Coach Mark!"
coachViews.bodyView.nextLabel.text = "Ok!"
return (bodyView: coachViews.bodyView, arrowView: coachViews.arrowView)
}
Once the dataSource is set up, you can start displaying the coach marks. You will most likely supply self to start. While the overlay adds itself as a child of the current window (to be on top of everything), the CoachMarksController will add itself as a child of the view controller you provide. That way, the CoachMarksController will receive size change events and react accordingly. Be careful, you can't call start in the viewDidLoad method, since the view hierarchy has to be set up and ready for Instructions to work properly.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.coachMarksController.start(in: .window(over: self))
}
You should always stop the flow, once the view disappear. To avoid animation artefacts and timing issues, don't forget to add the following code to your viewWillDisappear method. Calling stop(immediately: true) will ensure that the flow is stopped immediately upon the disappearance of the view.
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.coachMarksController.stop(immediately: true)
}
You're all set. For more examples you can check the Examples/ directory provided with the library.
You can customized the background color of the overlay using this property:
overlay.colorYou can also make the overlay blur the content sitting behind it. Setting this property to anything else than nil will disable the overlay.color:
overlay.blurEffectStyle: UIBlurEffectStyle?Last, you can make the overlay tappable. A tap on the overlay will hide the current coach mark and display the next one.
overlay.allowTap: Bool⚠️ The blurring overlay is not supported in app extensions.
If you dislike how the default cutout path looks like, you can customize it by providing a block to makeCoachMark(for:). The cutout path will automatically be stored in the cutoutPath property of the returning CoachMark object:
var coachMark = coachMarksController.helper.makeCoachMark(for: customView) {
(frame: CGRect) -> UIBezierPath in
// This will create an oval cutout a bit larger than the view.
return UIBezierPath(ovalIn: frame.insetBy(dx: -4, dy: -4))
}
frame will be the frame of customView converted in the coachMarksController.view referential, so don't have to worry about making sure the coordinates are in the appropriate referential. You can provide any kind of shape, from a simple rectangle to a complex star.
You can (and you should) provide custom views. A coach mark is composed of two views, a body view and an arrow view. Note that the term arrow might be misleading. It doesn't have to be an actual arrow, it can be anything you want.
A body view must conform to the CoachMarkBodyView protocol. An arrow view must conform to the CoachMarkArrowView protocol. Both of them must also be subclasses of UIView.
Returning a CoachMarkBodyView view is mandatory, while returning a CoachMarkArrowView is optional.
This protocol defines two properties.
nextControl: UIControl? { get } you must implement a getter method for this property in your view, this will let the CoachMarkController know which control should be tapped, to display the next coach mark. Note that it doesn't have to be a subview, you can return the view itself.
highlightArrowDelegate: CoachMarkBodyHighlightArrowDelegate? in case the view itself is the control receiving taps, you might want to forward its highlight state to the arrow view (so they can look as if they are the same component). The CoachMarkController will automatically set an appropriate delegate to this property. You'll then be able to do this:
override var highlighted: Bool {
didSet {
self.highlightArrowDelegate?.highlightArrow(self.highlighted)
}
}
Remember the following method, from the dataSource?
func coachMarksController(_ coachMarksController: CoachMarksController, coachMarkViewsAt index: Int, madeFrom coachMark: CoachMark) -> (bodyView: CoachMarkBodyView, arrowView: CoachMarkArrowView?) {
let coachViews = coachMarksController.helper.makeDefaultCoachViews(withArrow: true, arrowOrientation: coachMark.arrowOrientation)
}
When providing a customized view, you need to provide an arrow view with the approriate orientation (i. e. in the case of an actual arrow, pointing upward or downward). The CoachMarkController will tell you which orientation it expects, through the following property: CoachMark.arrowOrientation.
Browse the Example/ directory for more details.
You can choose in which context the coach marks will be displayed, by passing it to `start(in: PresentationContext). The available contexts are:
.newWindow(over: UIViewController, at: UIWindowLevel?) – A new window created at the given UIWindowLevel (not available in app extensions);.currentWindow(of: UIViewController) – The window displaying the given UIViewController;.viewController(_: UIViewController) – In the view of the given `UIViewContro