-->

Swift: visual glitches when presenting a Main and

2020-07-20 03:49发布

问题:

I've got an App that requires the person to sign in or create an account the first time the App is launched. (Although this is not generally recommended, this is quite a specific use case.)

Furthermore, if they are already signed in, when the App is updated I'd like to show them a screen telling them about new features (as Notes, Photos and Music do in iOS12).

The problem I have is when changing the ‘rootViewController’ of ‘window’ in AppDelegate, the app glitches and I can see the main view controller for a split second, before the onboarding view controller becomes visible.

Is there a way to avoid this? Can this logic be encapsulated in ViewControllers or in the AppDelegate?

Is there a single mechanism that I can use for one or more of these scenarios that does not rely on multiple implementations?

回答1:

Firstly, as you are dealing with multiple flows, this is where Storyboards can be used effectively. By default your Application uses Main.storyboard for your primary flow. Your onboarding/alternative flow can be contained in a secondary storyboard, eg. Onboarding.storyboard

This has a number of advantages:

  • in a team of developers, the work on each user flow can be separated
  • clearer source control (git)
  • separation of concerns

When your App launches, you can determine which flow should be presented. The logic for this can be contained in your AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let isFirstRun = true // logic to determine goes here
    if isFirstRun {
        showOnboarding()
    }
    return true
}

In order to show the Onboarding flow, it's worth considering how you'd like to handle the experience of dismissing it once the person using it has completed the journey, and which is semantically correct for what you are trying to create.

Approaches:

The two main approaches are:

  1. Swap the root view controller of the App's main window
  2. Present the Onboarding flow as a modal journey, overlapping the Main flow.

The implementation of this should be contained in an extension to AppDelegate.

Option 1: Swap the Root View Controller (Good)


There are benefits to switching the root view controller, although the transition options are limited to those supported by UIViewAnimationOptions, so depending on how you wish to transition between flows might mean you have to implement a custom transition - which can be cumbersome.

You can show the Onboarding flow by simply setting the UIApplication.shared.keyWindow.rootViewController

Dismissal is handled by utilizing UIView.transition(with:) and passing the transition style as a UIViewAnimationOptions, in this case Cross Dissolve. (Flips and Curls are also supported).

You also have to set the frame of the Main view before you transition back to it, as you're instantiating it for the first time.

// MARK: - Onboarding

extension AppDelegate {

    func showOnboarding() {
        if let window = UIApplication.shared.keyWindow, let onboardingViewController = UIStoryboard(name: "Onboarding", bundle: nil).instantiateInitialViewController() as? OnboardingViewController {
            onboardingViewController.delegate = self
            window.rootViewController = onboardingViewController
        }
    }

    func hideOnboarding() {
        if let window = UIApplication.shared.keyWindow, let mainViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
            mainViewController.view.frame = window.bounds
            UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: {
                window.rootViewController = mainViewController
            }, completion: nil)
        }
    }
}

Option 2: Present Alternative Flow Modally (Better)


In the most straightforward implementation, the Onboarding flow can simply be presented in a modal context, since semantically the User is on a single journey.

Apple Human Interface Guidelines – Modality:

Consider creating a modal context only when it’s critical to get someone’s attention, when a task must be completed or abandoned to continue using the app, or to save important data.

Presenting modally allows the simple option of dismissal at the end of the journey, with little of the cruft of swapping controllers.

Custom transitions are also supported in the standard way, since this uses the ViewController.present() API:

// MARK: - Onboarding

extension AppDelegate {

    func showOnboarding() {
        if let window = window, let onboardingViewController = UIStoryboard(name: "Onboarding", bundle: nil).instantiateInitialViewController() as? OnboardingViewController {
            onboardingViewController.delegate = self
            window.makeKeyAndVisible()
            window.rootViewController?.present(onboardingViewController, animated: false, completion: nil)
        }
    }

    func hideOnboarding() {
        if let window = UIApplication.shared.keyWindow {
            window.rootViewController?.dismiss(animated: true, completion: nil)
        }
    }
}