UISplitViewController和复杂视图层次结构

10
我正在进行一项iPad技术演示,但遇到了一个严重的技术问题。
我的应用概念利用了UISplitViewController,但不是作为整个应用程序的主要控制器。
应用程序的流程大致如下:
主屏幕(UIViewController) 列表->详细信息“目录”(UISplitViewController) 超级详细屏幕(UIViewController但也可以是SplitView的子级)。
问题出现在主屏幕和目录之间的流程中。一旦将UISplitViewController视图添加到UIWindow中,它就开始发生错误。
问题可以总结如下:
当UISplitView生成弹出视图时,它似乎会附着在其父视图上。从UIWindow子视图中删除UISplitView后,您将得到一个CoreGraphics异常,并且该视图将无法被删除。
当添加其他视图(在这种情况下可能是您返回的主屏幕)时,它们不会自动旋转,相反,由于CG异常而未能移除的UISplitView仍然继续响应旋转,导致无法处理的渲染错误。此时,添加任何视图,甚至重新添加SplitView,都会引起一系列渲染错误。
然后我尝试简单地将SplitView保留为“底部”视图,并不断向其上方添加和删除主屏幕,但这样做会失败,因为SplitView主导了方向更改调用,而主屏幕不会旋转,即使您调用[homeScreen becomeFirstResponder]。
您不能像UINavigationController那样将SplitView放入层次结构中,否则将直接运行时错误,因此该选项不可用。模态只是看起来很糟糕,并且也不鼓励使用。
我现在的推测是,处理此问题的唯一正确方法是“解除武装”UISplitViewController,以便可以从其父视图中删除它而不会引发未处理的异常,但我不知道如何做到这一点。
如果您想看到一个完全符合我的需求的应用程序,请查看iPad应用商店中的GILT Groupe。他们实现了这一点,但似乎编写了一个完整的自定义视图转换集。
非常感谢您的帮助。
6个回答

8

苹果公司指出:

分割视图控制器的视图应始终安装为应用程序窗口的根视图。您不应在导航或选项卡栏界面中呈现分割视图。

这意味着它应该是根视图而不是其他视图的子视图。即使他们补充说:

您不应将分割视图呈现为导航或选项卡栏界面的子视图

这也并不意味着您可以将其添加为任何其他控制器的子视图。 (抱歉)

我有一种感觉,你正在经历的是试图这样做的副产品。事实上,我很惊讶GILT Groupe的应用程序没有被拒绝。最近,苹果公司倾向于严格执行这些HIG指南。正如您已经发现的那样,当您尝试将它们添加到NavigationController时,它们会引发相当严重的运行时错误。


正确。实际上,我已经“解决”了这个问题,但是将SplitView作为根视图,并使用转换将所有其他视图呈现为全屏模态视图。我愿意打赌GILT可能正在使用一些自定义模态转换类似的东西。令人沮丧的是,苹果会发布一个如此有用的控件,并且带有如此多的限制。苹果的指南还规定不要过度使用模态...但在这种情况下他们给我们什么选择? - M. Ryan

4
我已经为自己解决了这个问题……实际上是通过将所有可能的全屏视图作为SplitView的模态窗口来实现的……
在我看来,这种方法并不理想,但如果你想在应用程序中只“有时”使用SplitView,则苹果没有给你太多选择余地。

嗨Jasconius,我正在尝试做和你一样的事情,我也在看Gilt应用程序。我需要完全相同的东西。你能发布一些样例代码吗?那将是非常有帮助的。谢谢。 - Nnp
是的,这正是我们在 Gilt 应用程序中所做的。SplitViewController 在另一个控制器内部确实表现不佳,因此我们只能使用模态视图来导航离开分割视图。 - jexe
请查看我的自定义UISplitViewController。它确实可以很好地满足您的需求。 - slatvick

4

我通过创建第二个UIWindow取得了一些成功。 我将UISplitViewController与其关联,并在需要显示分割视图时将其切换到主窗口。 它似乎按照我想要的方式工作,除了旋转时略有延迟和“wait_fences”日志消息。


很棒的想法。不过它在处理方向变化时有多么优雅呢? - M. Ryan
1
看起来运行得很好。无论哪个窗口可见,两个窗口的方向都会跟随设备的物理方向,就像你期望的那样。唯一的缺点是“wait_fences”错误在旋转时会产生轻微的视觉干扰。可能不够完美,但对我的演示来说足够了。 - g051051
你如何切换应用程序的窗口? - MikeN
在AppDelegate中,我创建了第二个UIWindow,添加了我的UISplitViewController,然后将其标记为隐藏。我对原始UIWindow进行正常设置。当用户想要查看UISplitViewController时,我向第二个UIWindow发送makeKeyAndVisible消息。完成后,我向原始窗口发送makeKeyAndVisible消息。 - g051051

0

我正在为这个问题苦苦挣扎。我一直在尝试各种方法来探究 UISplitViewController 的黑盒子,看看它的反应。

我似乎已经找到了一个解决方案,对我的情况似乎是有效的。

关键似乎是将第一个添加到 UIWindow 的视图正确初始化。我遇到的所有问题都源于设备方向的错误通知。显然,第一个添加的视图已经正确配置了这一点。

在我的情况下,我不想让 UISplitView 成为第一个视图。以下内容对我有效:

应用程序委托 application:didFinishLaunching 方法是特殊的。必须在此处将视图添加到 UIWindow 中。如果在其他地方完成,则无法正确配置。

本质上,魔法酱汁就是将分割视图作为第一个添加到窗口中的视图。然后,只要保留 UISplitViewController,就可以删除它。从那时起,您可以交换其他视图,包括 UISplitView,并且大多数事情似乎都没问题。

我仍然遇到了一些问题。除了分割视图之外的其他视图上的弹出窗口会混淆视图框架和工具栏按钮的位置,并显示在错误的位置。我将它们放在特定的位置,似乎可以处理这种情况。

如果在分屏视图上弹出了一个弹出窗口,并且您尝试查看另一个视图,则第二个视图的方向会混乱并显示为侧面。如果在弹出窗口显示之前访问该视图,则一切正常。我通过手动解除弹出窗口来修复了这个问题,然后再切换到其他视图。

以下是代码,如果有帮助的话。所有控制器都是appDelegate的实例变量

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // This also seems to work as good magic. Seems to set orientation and size properties that persist.
    [window addSubview:splitViewController.view];
    [splitViewController.view removeFromSuperview];

    [self switchToNewViewController:firstController];
    [window makeKeyAndVisible];
    return TRUE;
}

- (void)switchToNewViewController:(UIViewController *)newViewController {
    [popoverController dismissPopoverAnimated:FALSE];
    if (newViewController != currentViewController) {
        [currentViewController removeFromSuperview];
        currentViewController = newViewController;
        [window addSubView:newViewController.view];
     }
}

这是一个很好的见解,关于在appDidFinishLaunching中添加然后删除分割视图。 "根视图控制器"和错误的方向通知一直是一个已经确立的问题,这完全加剧了SplitView不能成为子视图的事实。在一个理性的宇宙中(即:基本上任何不使用SplitView的应用程序),您总是有一个通用的RVC将方向信息发送给其子级。Splitview打破了这个规则。最好自己制作Splitview。 - M. Ryan

0

我只是想说,我也遇到了同样的问题,在这个论坛主题中找到了解决方法,并按照上面g051051的建议进行操作。这对我来说完美地解决了问题。我没有看到任何故障,并且在设备控制台中也没有关于wait_fences的消息。

我只是使用IB在主XIB中创建了两个UIWindow对象,正常创建了UISplitViewController和另一个派生自UIViewController的控制器实例(我用于全屏显示)。我只需通过将每个UIWindow的rootViewController连接到其相应的控制器来简单地将它们连接起来。

在application:didLaunch...:方法中,我可以决定向哪个窗口发送makeKeyAndVisible方法,以及将哪个窗口设置为隐藏。当用户想要来回切换时,我只需要向其中一个发送makeKeyAndVisible,然后在另一个上设置hidden属性,就这么简单。

如上所述,所有与旋转相关的消息都会适当地发送到每个控制器,无论哪个控制器与当前可见窗口相关联。

总之,这对我来说非常好用,而且设置起来也很容易。


实际上,有一个小问题:在XIB中设置UIWindows的rootViewControllers会导致一个小问题。问题在于,当以纵向模式启动时,UISplitViewControllerDelegate的方法splitViewController:willHideViewController:...方法最初不会被调用,因此没有添加UIBarButtonItem的时机。如果将splitViewController.view明确添加为application:didFinishLaunchingWithOptions:中UIWindow的子视图,则可以正确地调用此初始委托方法。 - Eric

0

除非你是为越狱设备开发,否则违背苹果的规定和意愿并不是一个好主意。正如Jann和Jasconius上面所说,这意味着保持splitView控制器视图根,不要过度使用模态(含糊不清)和不使用多个窗口。

此外,Gilt应用程序仅在美国提供。

我也一直在尝试寻找解决方案,并像Tuannd所说的那样以编程方式从窗口中删除视图,但是横向渲染错误是不可原谅的。

@ Jasconius,你同时呈现的模态的最大数量是多少?


好吧,最多只能有一个,因为这似乎是你可以轻松应付的全部。如果必要,我会手动放置子视图。我现在已经离开了那个项目,开始为 iPad 开发自定义控件了。iPad 的方向管理真是太糟糕了。 - M. Ryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接