swiftuiviewcontrolleruikituiwindowpresentmodalviewcontroller

How to modally present a ViewController from SceneDelegate?


I have an OnboardingViewController which available to access from app's settings in a modal style.

When a user opens the app first time I want to show him the OnboardingVC in the same modal style (with an upper tabs effect, like so: Screenshot) as it loads if to present it from the Settings.

SceneDelegate setup:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let scene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: scene)
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let controller = storyboard.instantiateViewController(withIdentifier: "OnboardingViewController") as! OnboardingViewController
    window?.rootViewController = controller
    window?.makeKeyAndVisible()
 }

The problem it's always appear in a full screen, without the above modal tabs effect at the top.

How to solve it?


Solution

  • You can't present a UIViewController (modally or fullscreen) which is window's rootViewController.

    To show Onboarding screen when user open app for the first time, set Home Screen/Login Screen (or any screen you want) as rootViewController in SceneDelegate. Then from that UIViewController you can check if user open app for first time or not. Depending on that you can show the Onboarding screen.

    You must embed the rootViewController in a UINavigationViewController to present/push another UIViewController

    In SceneDelegate.swift modify the code like below.

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: scene)
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
        //embed in UINavigationController
        let navController = UINavigationController(rootViewController: controller)
        window?.rootViewController = navController
        window?.makeKeyAndVisible()
     }
    

    Then in the Home screen (i assume it as HomeViewController) check the status of first opening and present Onboarding screen.

    class HomeViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let controller = storyboard.instantiateViewController(withIdentifier: "OnboardingViewController") as! OnboardingViewController
            controller.modalPresentationStyle = .formSheet
            self.present(controller, animated: true)
        }
    }
    
    

    You can get the rootViewController from anywhere in the app by using the code below. And then present any ViewController you want to show.

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let controller = storyboard.instantiateViewController(withIdentifier: "OnboardingViewController") as! OnboardingViewController
            controller.modalPresentationStyle = .formSheet
    let rootVC = UIApplication.shared.windows.first!.rootViewController
    rootVC?.present(controller, animated: true)