Search code examples
iosswiftuiviewcontrollerlifecycle

iOS Swift: Best practices for starting app with an alternative UIViewController


TLDR version: I try to push the user to a specific UIVIewController in an iOS App using Swift via methods to check for a saved object. If I put the push to the VC in the ViewDidAppear, it takes several seconds to fire. If I put it in viewDidLayoutSubviews, it throws the warning "Unbalanced calls to begin/end appearance transitions for <ProjectShare.LaunchViewController: 0x7f85a2e37520>."

I want to know the best way to accomplish this functionality so I have a good user experience and pass Apple submission.

Details: I am part of a small class group working on a small software project for iOS using Swift, specifically focused on a group experience using the MultipeerConnectivity framework. We check for a UserInfo object persisted on the device upon launch of the app and if one is not found, we push the user to a screen to add a username and image to share with other users.

The methods for this are relatively straightforward. We check for the encoded object upon launch via the AppDelegate:

let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(self.documentsPath!) {
  var userForLoad = UserInfo.loadTheObject()
  self.defaultUser = userForLoad as UserInfo!
} else {
  self.defaultUser = nil
}

This works well, all of the persistence and such are working very well. The app then goes to its initial ViewController, here known as the LaunchViewController: this ViewController is the main menu for the app and will 99.9% of the time be the best starting point, and is needed immediately after creating the UserInfo object. If the LaunchViewController finds that the appDelegate.defaultUser object is nil, then I want to force the user to the CreateUserViewController to enter a name and capture an image.

let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let myUserTest = appDelegate.defaultUser as UserInfo!
if myUserTest == nil {
    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
    let destinationVC = storyboard.instantiateViewControllerWithIdentifier("CREATEUSER_VC") as CreateUserViewController!
    let presentingVC = storyboard.instantiateViewControllerWithIdentifier("LAUNCHVIEW_VC") as LaunchViewController!
    destinationvVC.delegate = self
    self.presentViewController(destinationvVC, animated: false, completion: { () -> Void in
      println("Finished presenting CreateUserViewController.")
    })
}

This also works in that it presents the proper view controller, but the timing is tricky.

If I put this method in viewDidLoad or in viewWillAppear, it fails to fire. if I put it in viewDidAppear, it appears after about two seconds, which isn't a smooth user experience. if I put it in viewDidLayoutSubviews, i get the following warning, which I presume is a submission fail case:

Unbalanced calls to begin/end appearance transitions for <ProjectShare.LaunchViewController: 0x7f85a2e37520>.

I get similar results if I use a performSegueWithIdentifier method.

I know that it's possible to select the initial viewController programmatically from the AppDelegate in didFinishLaunchingWithOptions (though i am not familiar with how to do it), but that seems like a poor architectural choice for something that happens exactly once in the user's relationship with the software.

Basically, looking for help making this happen smoothly for the user and in a manner that is not an apple submission fail case.


Solution

  • I haven't seen your storyBoard but I recommend it has a UINavigationController so you could easily add the CreateUserVC to the parent-childen hierarchy and don't have to perform a present.

    I've written this minimal example:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    
        //        var viewController: ViewController = ViewController (nibName:nil,bundle:nil)
        self.navigation = UINavigationController (rootViewController: ViewController())
        self.navigation?.addChildViewController(NewViewController())
        self.window!.rootViewController = self.navigation
        self.window!.makeKeyAndVisible()
        return true
    }
    

    of course you should check for the UserDefaults before adding the CreateUserVC as a childViewController. But if you do add it, the app will start with the CreateUserVC presented and the Back button on its navigation that leads to LaunchVC.

    NOTE: You can have your navigation hidden if your app doesn't show one.