iphone - 在 UISplitViewController 和其他 View Controller 之间切换的最佳方式?

标签 iphone ipad user-interface uisplitviewcontroller appstore-approval

我正在编写一个 iPad 应用程序。应用程序中的屏幕之一非常适合使用 UISplitViewController。但是,应用程序的顶层是主菜单,我不想使用 UISplitViewController。这带来了一个问题,因为苹果声明:

  1. UISplitViewController 应该是应用程序中的顶级 View Controller ,即它的 View 应该添加为 UIWindow

  2. 如果使用,UISplitViewController 应该在应用程序的生命周期内一直存在——即不要从 UIWindow 中删除其 View 并放置另一个 View ,反之亦然

经过阅读和实验,似乎满足 Apple 要求和我们自己的要求的唯一可行选择是使用模式对话框。因此,我们的应用程序在根级别有一个 UISplitViewController (即,它的 View 作为 UIWindow 的 subview 添加),并且为了显示我们的主菜单,我们将其作为全屏模式对话框推送到 UISplitViewController 上。然后通过关闭主菜单 View Controller 模式对话框,我们实际上可以显示我们的分割 View 。

这个策略看起来效果很好。但这引出了一些问题:

1)是否有更好的方式来构造这个,没有模式,也满足所有提到的要求?通过将主 UI 作为模式对话框推送来出现似乎有点奇怪。 (模态框应该用于集中的用户任务。)

2) 我是否会因为我的方法而面临被应用商店拒绝的风险?根据苹果的人机界面指南,这种模式策略可能“滥用”模式对话框。但他们还给了我什么其他选择呢?无论如何,他们会知道我正在这样做吗?

最佳答案

我真的不相信在 UISplitViewController (例如登录表单)之前显示一些 UIViewController 的概念会如此复杂,直到我不得不创建这种类型 View 层次结构。

我的示例基于 iOS 8 和 XCode 6.0 (Swift),所以我不确定这个问题以前是否以同样的方式存在,或者是由于 iOS 8 引入的一些新错误,但从在我发现的所有类似问题中,我没有看到这个问题的完整“不是很老套”的解决方案。

在最终找到解决方案之前(在这篇文章的末尾),我将引导您完成我尝试过的一些事情。每个示例都基于在未启用 CoreData 的情况下从主从模板创建新项目。

<小时/>

第一次尝试(模态转场到 UISplitViewController):

  1. 创建新的 UIViewController 子类(例如 LoginViewController)
  2. 在 Storyboard中添加新的 View Controller ,将其设置为初始 View Controller (而不是 UISplitViewController)并将其连接到 LoginViewController
  3. 将 UIButton 添加到 LoginViewController 并创建从该按钮到 UISplitViewController 的模式转场
  4. 从 AppDelegate 的 didFinishLaunchingWithOptions 中移动 UISplitViewController 的样板设置代码到 LoginViewController 的 prepareForSegue

这几乎成功了。我说几乎,因为在使用 LoginViewController 启动应用程序并点击按钮并转到 UISplitViewController 后,会出现一个奇怪的错误:在方向更改时显示和隐藏主视图 Controller 不再是动画的。

经过一段时间的努力解决这个问题并且没有真正的解决方案,我认为它与 UISplitViewController 必须是 rootViewController 的奇怪规则有某种联系(在这种情况下它不是,LoginViewController 是)所以我放弃了这个不太完美的解决方案。

<小时/>

第二次尝试(来自 UISplitViewController 的模态转场):

  1. 创建新的 UIViewController 子类(例如 LoginViewController)
  2. 在 Storyboard中添加新的 View Controller ,并将其连接到 LoginViewController(但这次将 UISplitViewController 保留为初始 View Controller )
  3. 创建从 UISplitViewController 到 LoginViewController 的模态转场
  4. 将 UIButton 添加到 LoginViewController 并从该按钮创建展开转场

最后,将此代码添加到 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

这两个例子都会产生同样的一些不好的事情:

  1. 控制台输出:Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
  2. UISplitViewController 必须在 LoginViewController 模态连接之前首先显示(我宁愿只显示登录表单,这样用户在登录之前就看不到 UISplitViewController)
  3. Unwind segue 不会被调用(这完全是另一个错误,我现在不打算讨论这个故事)
<小时/>

解决方案(更新rootViewController)

我发现正确工作的唯一方法是动态更改窗口的 rootViewController:

  1. 为 LoginViewController 和 UISplitViewController 定义 Storyboard ID, 并向AppDelegate添加某种loggedIn属性。
  2. 根据此属性,实例化适当的 View Controller ,然后将其设置为 rootViewController。
  3. 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

        // create and setup appropriate rootViewController
        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
        }

        // update app's rootViewController
        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 中正常工作。

关于iphone - 在 UISplitViewController 和其他 View Controller 之间切换的最佳方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4213097/

相关文章:

iphone - 我可以将 CAArchive 类添加到我的 iPhone 项目以序列化数据吗?如果是这样,如何?

ios - UIScrollViews 并在其上添加组件

iPhone-SDK : UITextView doesn't show the newline key

iphone - 以编程方式点击 HTML href 以更新应用程序

javascript - 如何从用户代理字符串中找出所有版本的 iPad?

java - 不调整窗口大小不显示内容

android - iOS:如何实现类似 Android 的 startActivityForResult 的行为

iphone - 关于通用 OpenGL ES 应用程序(iPhone 和 iPad)的建议?

java - 通过网格/ ListView 将 View 推离屏幕

android - 将 iPhone 图形移植到 Android 时有经验法则吗?