我之前真的不相信在UISplitViewController之前显示某些UIViewController(例如登录表单)这个概念会变得如此复杂,直到我不得不创建这种视图层次结构。
我的示例基于iOS 8和XCode 6.0(Swift),因此我不确定这个问题是否以同样的方式存在于以前,或者是由于iOS 8引入了一些新错误,但是从我找到的所有类似问题中,我没有看到完整的“不太hacky”的解决方法。
在本文末尾提供解决方案之前,我将指导您通过我尝试过的一些事情。每个示例都基于创建未启用CoreData的主细节模板的新项目。
第一次尝试(modal segue到UISplitViewController):
- 创建新的UIViewController子类(例如LoginViewController)
- 在故事板中添加新的视图控制器,并将其设置为初始视图控制器(而不是UISplitViewController),然后将其连接到LoginViewController
- 将UIButton添加到LoginViewController并从该按钮创建modal segue到UISplitViewController
- 将UISplitViewController的模板设置代码从AppDelegate的
didFinishLaunchingWithOptions
移动到LoginViewController的prepareForSegue
这几乎起作用了。我说几乎,因为当使用LoginViewController启动应用程序并点击按钮并segue到UISplitViewController时,会出现奇怪的错误:在方向更改时显示和隐藏主视图控制器不再有动画效果
在与这个问题斗争一段时间并没有找到真正的解决方法后,我认为它与那个奇怪的规则相关,即UISplitViewController必须是rootViewController(但在这种情况下它不是,LoginViewController是),因此我放弃了这个不太完美的解决方案。
第二次尝试(从UISplitViewController进行modal segue):
- 创建新的UIViewController子类(例如LoginViewController)
- 在故事板中添加新的视图控制器,并将其连接到LoginViewController(但这次保留UISplitViewController作为初始视图控制器)
- 从UISplitViewController创建模态segue到LoginViewController
- 在LoginViewController中添加UIButton并创建unwind segue
最后,在AppDelegate的didFinishLaunchingWithOptions
之后添加以下代码以设置UISplitViewController的模板代码:
window?.makeKeyAndVisible()
splitViewController.performSegueWithIdentifier("segueToLogin", sender: self)
return true
或者尝试使用这段代码代替:
window?.makeKeyAndVisible()
let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
splitViewController.presentViewController(loginViewController, animated: false, completion: nil)
return true
这两个示例都会产生以下几个问题:
- 控制台输出:
Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
- 必须先显示 UISplitViewController,然后才能以模态方式跳转到 LoginViewController(我希望仅在用户登录之前显示登录表单,而不是让用户在登录之前看到 UISplitViewController)
- 退回跳转未被调用(这是完全不同的错误,我现在不想深入讨论)
解决方案(更新 rootViewController)
我找到的唯一有效的方法是在运行时更改窗口的 rootViewController:
- 为 LoginViewController 和 UISplitViewController 定义故事板 ID,并向 AppDelegate 添加某种 loggedIn 属性。
- 根据此属性,实例化适当的视图控制器,然后将其设置为 rootViewController。
- 在
didFinishLaunchingWithOptions
中不使用动画执行此操作,但在从 UI 中调用时使用动画。
下面是 AppDelegate 的示例代码:
var loggedIn = false
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
setupRootViewController(false)
return true
}
func setupRootViewController(animated: Bool) {
if let window = self.window {
var newRootViewController: UIViewController? = nil
var transition: UIViewAnimationOptions
if !loggedIn {
let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
newRootViewController = loginViewController
transition = .TransitionFlipFromLeft
} else {
let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController
let controller = masterNavigationController.topViewController as MasterViewController
newRootViewController = splitViewController
transition = .TransitionFlipFromRight
}
if let rootVC = newRootViewController {
if animated {
UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in
window.rootViewController = rootVC
}, completion: nil)
} else {
window.rootViewController = rootVC
}
}
}
}
这是来自LoginViewController的示例代码:
@IBAction func login(sender: UIButton) {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
delegate.loggedIn = true
delegate.setupRootViewController(true)
}
如果在iOS 8中有更好/更清晰的方法使其正常工作,我也想听听。