UITabBarController、MoreNavigationController 和设备旋转的圣杯 [英] UITabBarController, MoreNavigationController and the Holy Grail of Device Rotation

查看:17
本文介绍了UITabBarController、MoreNavigationController 和设备旋转的圣杯的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新:请先查看我对这个问题的回答.这似乎是一个错误.已经创建了一个最小的测试用例,并向 Apple 提交了一份报告.(自 iPhone OS 3.1 起已修复.)

这是我好近了!"中的一个谜题.部门.

我有一个基于标签栏的 iPhone 应用程序.每个选项卡都有一个 UINavigationController 与通常的嫌疑人(导航栏、表格视图......这反过来又可以导致另一个 VC 等).

现在,这些较低级别的 VC 之一将用于纵向 横向模式.但是有一个问题.我们的景观友好型 VC 的 shouldAutorotateToInterfaceOrientation: 不会被直接调用!怎么办?

这就是我们所做的.在我在自己的文件中实现的标签栏控制器中,我有这个:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];}

这最终将请求传播到我的横向友好型 VC,它也响应此消息.我所有的其他 VC 都没有实现这个方法,所以他们只是使用默认的纵向.

问题解决了!!!耶!

嗯,不完全是. :(

当从标签栏控制器的 MoreNavigationController 深处调用我的横向友好型 VC 时,似乎情况不太好.

我决定比较/对比从前四个标签栏UINavigationControllers之一调用的VC和从MoreNavigationController.这将有点超详细,所以请耐心等待.希望逐个播放证明对侦破有用.

当应用加载时,标签栏控制器的 shouldAutorotate... 方法有几个初始调用.在这些早期情况下,selectedViewController 为 nil.但是,我们最终加载完成,选择了初始选项卡项,一切正常.

没错.首先,让我们从前四个标签栏项目中选择一个并深入了解我们的 VC.

我们将选择第三个导航栏项目,这就是第三个导航控制器.我们深入了解支持轮换的 VC.快速检查确认父级确实是我们标签栏的视图控制器列表中的第三个导航控制器.好!

让我们旋转设备.标签栏控制器被要求自动旋转(参见上面的代码).我们观察到 selectedViewController 也是第三个导航控制器,加上导航控制器的顶部和可见视图控制器都设置为我们可信赖的支持旋转的 VC.

因此,标签栏控制器会将 shouldAutorotate 消息转发到第三个导航控制器……但我们的旋转友好型 VC 最终会收到消息.(我在这里没有做任何特别的事情.也许所需的 VC 会收到消息,因为它是顶部和/或可见的 VC?)无论如何,我们旋转到横向,调整大小,一切都很好.大获成功!"

现在让我们点击返回按钮并弹出 VC 堆栈,在此过程中保留横向模式.再次查询标签栏控制器.

这里有一点时间.我们导航控制器的 topViewController 仍然是旋转友好的 VC,但 visibleViewController 现在设置为 UISnapshotModalViewController!呵呵.以前从未见过这个……但是 Erica Sadun 有.看起来它是用于消失的视图控制器"(在这种情况下当然是真的 - 它正在消失).

当我继续执行时,可见 VC 保持为快照,但顶部 VC 最终会更改为堆栈中的下一个 VC,因为我的特殊 VC 最终消失了.很公平.

所以这是一切正常的场景.

现在让我们尝试相同的测试,只是这次我们将转到 MoreNavigationController(更多选项卡栏项)并深入到与之前相同的 VC 类.在我的例子中,它恰好是标签栏控制器的 VC 列表中的第 7 个.

我们进入旋转感知 VC 并且......这次它被要求直接旋转!标签栏控制器没有被要求获得旋转权限.嗯.

快速检查父 VC 表明它是一个 MoreNavigationController.好的,有道理.

现在让我们尝试旋转设备.什么都不会调用.我们的断点都没有被命中.不在我们的 VC 中.不在我们的标签栏控制器中.(嗯?!?!)

噢-kaaaay.让我们弹出堆栈,回到同一个 VC 并再次尝试旋转.奇怪的.现在我们在标签栏控制器中接到一个请求自动旋转权限的电话.在这里,选择的控制器是我们信任的导航控制器 (#7),但这次它的 visibleViewControllertopViewControllerSET TO NIL!p>

一旦我们从这里继续,调试器控制台中会出现一条神秘消息:

<块引用>

使用两阶段旋转动画.到使用更平滑的单级动画,这个应用程序必须去除两阶段法实现.

神秘因为我没有使用两阶段旋转动画!我的源代码中的任何地方都没有 SecondHalf 方法变体.

唉,我的旋转感知 VC 从来没有被告知旋转正在发生(即使旋转确实发生在屏幕上),所以我的观点当然被弄乱了.混乱和悲伤随之而来.:(

此时我们甚至不会费心弹出堆栈.

我认为 View Controller 文档暗示了可能的问题:

<块引用>

如果您想执行自定义定向期间的动画改变,你可以在两个之一中做到这一点方法.用于改变方向分两步发生,带有通知发生在开头、中间、和旋转的终点.但是,在 iPhone OS 3.0 中,支持添加用于执行定向一步改变.使用一步方向变化往往更快比旧的两步过程,是一般推荐用于任何新的代码.

我想知道 MoreNavigationController 是否仍在响应两步流程,因此是否会阻止任何使用一步流程的尝试?请注意,如果您回复两步消息,则一步变体将不起作用(同样,根据文档).我没有回应他们,但我有一个隐秘的怀疑幕后是什么.

事实上,如果我将单步方法注释掉,并尝试响应willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:,我确实得到了备忘录!但它仍然不能非常干净地从堆栈中弹出(就视觉效果而言).更奇怪的是:willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: 不会被调用,即使我试图在 shouldAutorotateToInterfaceOrientation: 中偷偷调用 self(使用 FirstHalf 消息).它在跟踪期间立即返回,就好像我从未定义过它一样.叹息.

所以这就是逐个播放.

总之,任何人 是否成功地处理了从标签栏控制器的 MoreNavigationController 中调用的 VC 的一步设备旋转?有求知欲的人想知道!

解决方案

Apple 建议不要继承 UITabBarController,因此我找到了一种使用类别来处理自动旋转的简单方法.它不能使用 More... 视图控制器修复您的错误,但我认为这是一种更适合 Apple 的完成工作的方式(并且意味着您的子类化更少).

为了使我的应用程序中的每个选项卡都能正确自动旋转,我在我的自定义视图控制器中定义了 -shouldAutorotateToInterfaceOrientation:,但它们都在 UITabBarController 内的 UINavigationControllers 内,因此消息不会沿着链发送到我的 VC直到那两个也回应.所以我在我的应用委托文件中添加了以下几行:

<块引用>

添加到 MyAppDelegate.h 的底部

@interface UITabBarController (MyApp)@结尾@interface UINavigationController (MyApp)@结尾

<块引用>

添加到 MyAppDelegate.m 的底部

@implementation UITabBarController (MyApp)- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {返回是;}@结尾@implementation UINavigationController (MyApp)- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {返回是;}@结尾

UPDATE: See my answer to this question first. This appears to be a bug. A minimal test case has been created and a report has been filed with Apple. (Fixed as of iPhone OS 3.1.)

Here's a puzzler from the "I'm so close!" department.

I have a Tab Bar-based iPhone app. Each tab features a UINavigationController with the usual suspects (nav bar, table view ... which in turn can lead to another VC, etc.).

Now, one of those lower-level VCs is to be used in portait and landscape modes. But there's a problem. Our landscape-friendly VC's shouldAutorotateToInterfaceOrientation: won't get called out-of-the-box! What to do?

Here's what we do. In my Tab Bar Controller, which I have implemented in its own file, I have this:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
     return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}

This ends up propagating the request to my landscape-friendly VC, which also responds to this message. All my other VCs don't implement this method, so they simply go with the default portrait orientation.

Problem solved!!! Yay!

Well, not quite. :(

Seems like things don't go so well when my landscape-friendly VC is invoked from within the depths of the tab bar controller's MoreNavigationController.

I decided to compare/contrast between a VC called from within one of the first four tab bar UINavigationControllers ... and that same VC called from within the MoreNavigationController. This is going to be a bit ultra-detailed, so bear with me. Hopefully the play by play proves useful for sleuthing things out.

When the app loads, there are several initial calls to the tab bar controller's shouldAutorotate... method. In these early cases, selectedViewController is nil. However, we eventually finish loading, the initial tab item is selected, and all is well.

Right. First, let's pick one of the first four tab bar items and drill down to our VC.

We'll pick third nav bar item, so that's the third nav controller. We drill down to our VC that supports rotation. A quick inspection confirms that the parent is indeed the third nav controller from our tab bar's view controller list. Good!

Let's rotate the device. The tab bar controller is asked to autorotate (see the above code). We observe that selectedViewController is also that third nav controller, plus the nav controller's top and visible view controllers are both set to our trusty VC that supports rotation.

Thus, the tab bar controller will forward the shouldAutorotate message over to the third nav controller ... but our rotation-friendly VC ultimately gets the message. (I'm not doing anything special here. Maybe the desired VC gets the message because it's the top and/or visible VC?) In any event, we rotate to landscape, things are resized, and all is well. "Great Success!"

Now let's hit that back button and pop the VC stack, leaving landscape mode in the process. The tab bar controller is queried again.

Time for a little aside here. The topViewController for our nav controller is still that rotation-friendly VC, but visibleViewController is now set to UISnapshotModalViewController! Heh. Never saw this one before ... but Erica Sadun has. Looks like it's for "disappearing view controllers" (which in this case is certainly true - it's disappearing alright).

As I keep stepping through, the visible VC stays as Snapshot, but the top VC eventually changes to the next VC on the stack, since my special VC is eventually gone. Fair enough.

So that's the scenario where everything works well.

Now let's try the same test, only this time we're going to go to the MoreNavigationController (the More tab bar item) and drill down to the same VC class as before. In my case it happens to be the 7th one in the tab bar controller's VC list.

We enter the rotation-aware VC and ... this time it gets asked to rotate directly! The Tab Bar Controller is not asked for permission to rotate at all. Hmm.

A quick check of the parent VC shows it is a MoreNavigationController. OK, that makes sense.

Now let's try to rotate the device. NOTHING GETS CALLED. None of our breakpoints get hit. Not in our VC. Not in our tab bar controller. (Huh?!?!)

O-kaaaay. Let's pop the stack, go back into the same VC and try to rotate again. Weird. NOW we get a call in the Tab Bar Controller asking for autorotation permission. Here, the selected Controller is our trusty Nav controller (#7), but this time its visibleViewController and topViewController are SET TO NIL!

Once we continue from here, a mysterious message appears in the debugger console:

Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.

Mysterious because I'm not using two-stage rotation animation! No SecondHalf method variants are in play anywhere in my source code.

Alas, my rotation-aware VC is never told that rotation is occurring (even though rotation does occur on-screen), so of course my view is all fouled up as a result. Mayhem and sadness ensue. :(

We won't even bother popping the stack at this point.

I think the View Controller doc hints at the possible problem:

If you want to perform custom animations during an orientation change, you can do so in one of two ways. Orientation changes used to occur in two steps, with notifications occurring at the beginning, middle, and end points of the rotation. However, in iPhone OS 3.0, support was added for performing orientation changes in one step. Using a one-step orientation change tends to be faster than the older two-step process and is generally recommended for any new code.

I wonder if MoreNavigationController is still responding to the two-step process, and is thus tripping up any attempts to use the one-step process? Note that, if you respond to the two-step messages, the one-step variant will not work (again, per the docs). I'm not responding to them, but I have a sneaking suspicion something behind-the-scenes IS.

In fact, if I comment out the single-step method, and try to respond to willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:, I do get the memo! But it still doesn't pop off the stack very cleanly (in terms of visuals). Even stranger: willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: is NOT called, even when I tried to sneak a call to self (using the FirstHalf message) in shouldAutorotateToInterfaceOrientation:. It returns immediately during a trace, as if I never even defined it. Sigh.

So that's the play-by-play.

In summary, has anyone successfully handled one-step device rotation for a VC invoked from within a Tab Bar Controller's MoreNavigationController? Inquiring minds want to know!

解决方案

Apple advises against subclassing UITabBarController, so I found an easy way to handle autorotation using categories instead. It doesn't fix your bug with the More... view controllers, but I think it's a more Apple-friendly way of getting the job done (and means less subclassing for you).

To make every tab in my application autorotate properly, I've defined -shouldAutorotateToInterfaceOrientation: in my custom view controllers, but they are all inside UINavigationControllers within a UITabBarController, so the message won't get sent down the chain to my VC until those two also respond. So I added the following lines to my app delegate files:

Added to the bottom of MyAppDelegate.h

@interface UITabBarController (MyApp)
@end

@interface UINavigationController (MyApp)
@end

Added to the bottom of MyAppDelegate.m

@implementation UITabBarController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return YES;
}
@end

@implementation UINavigationController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return YES;
}
@end

这篇关于UITabBarController、MoreNavigationController 和设备旋转的圣杯的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆