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.
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.