Sometimes called Coachmarks, a Wizard, or an Onboarding Flow, this common UX pattern is used to guide first time users to an app on its features in your UI.

Instructional Overlay

Here’s a quick and simple way to implement this pattern:

private func buildButton(with title: String) -> UIButton {
    let button = UIButton(type: .system)
    button.backgroundColor = .systemOrange
    button.setTitle(title, for: .normal)
    return button
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    let buttonText = "Put me in coach!"
    
    let button = buildButton(with: buttonText)
    button.translatesAutoresizingMaskIntoConstraints = false
    
    let overlay = UIView(frame: .zero)
    overlay.translatesAutoresizingMaskIntoConstraints = false
    overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
    
    let coachButton = buildButton(with: buttonText)
    coachButton.translatesAutoresizingMaskIntoConstraints = false
    coachButton.isUserInteractionEnabled = false
    
    let coachLabel = UILabel(frame: .zero)
    coachLabel.translatesAutoresizingMaskIntoConstraints = false
    coachLabel.textColor = .white
    coachLabel.text = "Get player in the game"

    view.addSubview(button)
    view.addSubview(overlay)
    view.addSubview(coachButton)
    view.addSubview(coachLabel)
    
    NSLayoutConstraint.activate([
        button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
        button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
        
        coachButton.centerXAnchor.constraint(equalTo: button.centerXAnchor),
        coachButton.centerYAnchor.constraint(equalTo: button.centerYAnchor),
        
        coachLabel.centerXAnchor.constraint(equalTo: coachButton.centerXAnchor),
        coachLabel.centerYAnchor.constraint(equalTo: coachButton.bottomAnchor, constant: 20),

        overlay.topAnchor.constraint(equalTo: self.view.topAnchor),
        overlay.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
        overlay.leftAnchor.constraint(equalTo: self.view.leftAnchor),
        overlay.rightAnchor.constraint(equalTo: self.view.rightAnchor),
    ])
}

The idea here is that you have a duplicate button on top of a semi-transparent overlay. We use Auto Layout to constrain coachButton to the real button, and we also disable isUserInteractionEnabled since we don’t actually want the user to be able to select it in this flow.

Instructional Overlay Views Exploded

Also, note how we’re using buildButton(with:) to instantiate the buttons so we make sure we’re actually constructing the buttons the same way for both button and coachButton.


Did you find this helpful? Get updates when I post more like this by subscribing to the Nerd Tower newsletter.