UITabBarController、MoreNavigationController 和设备旋转的圣杯

发布于 2024-07-30 20:10:26 字数 4178 浏览 3 评论 0原文

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

这是“I'm so close!”中的一个谜题。 部门。

我有一个基于标签栏的 iPhone 应用程序。 每个选项卡都有一个 UINavigationController ,其中包含常见的内容(导航栏、表格视图......这反过来又可能导致另一个 VC 等)。

现在,这些较低级别的 VC 之一将用于纵向和横向模式。 但有一个问题。 我们的景观友好 VC 的 shouldAutorotateToInterfaceOrientation: 不会被调用为开箱即用! 该怎么办?

这就是我们所做的。 在我在自己的文件中实现的选项卡栏控制器中,我有这样的内容:

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

这最终会将请求传播到我的景观友好型 VC,该 VC 也会响应此消息。 我的所有其他 VC 都没有实现此方法,因此它们只是采用默认的纵向方向。

问题解决了!!! 是的!

嗯,不完全是。 :(

当我的景观友好型 VC 从标签栏控制器的 MoreNavigationController 深处调用时,事情似乎进展得不太顺利。

我决定对从前四个选项卡栏 UINavigationControllers 中调用的 VC 与从 UINavigationControllers 中调用的同一 VC 进行比较/对比。 MoreNavigationController。这将是一个非常详细的内容,所以请耐心等待,

当应用程序加载时,会有几次对选项卡的初始调用 。 bar 控制器的 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 列表中的第七个。

我们输入旋转感知的 VC 并且...这次它被要求直接旋转! 标签栏控制器根本根本不需要获得旋转权限。 唔。

快速检查父 VC 会发现它是一个 MoreNavigationController。 好吧,这是有道理的。

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

O-kaaaay。 让我们弹出堆栈,返回到同一个 VC 并尝试再次旋转。 诡异的。 现在我们在选项卡栏控制器中收到一个请求自动旋转权限的调用。 在这里,选定的控制器是我们值得信赖的导航控制器 (#7),但这次它的 visibleViewControllertopViewController设置为 NIL

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

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

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

唉,我的旋转感知 VC 从未被告知旋转正在发生(即使旋转确实发生在屏幕上),所以我的视图当然因此而变得混乱。 混乱和悲伤随之而来。 :(

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

我认为视图控制器文档暗示了可能的问题:

如果你想执行自定义 定向期间的动画 更改,您可以通过以下两种方式之一进行更改 方法。 方向改变用于 分两步进行,并发出通知 发生在开头、中间、 和旋转的终点。 然而,在 iPhone OS 3.0 中,支持是 添加用于执行定向 一步改变。 使用一步法 方向变化往往更快 比旧的两步过程 通常推荐给任何新的 代码。

我想知道 MoreNavigationController 是否仍在响应两步过程,从而阻碍任何使用一步过程的尝试? 请注意,如果您响应两步消息,则一步变体将不起作用(同样,根据文档)。 我没有回应他们,但我隐隐怀疑幕后有什么事情。

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

这就是一场一场的比赛。

总之,是否有人成功处理了从选项卡栏控制器的 MoreNavigationController 中调用的 VC 的一步设备旋转? 好奇心想知道!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

未央 2024-08-06 20:10:26

Apple 建议不要对 UITabBarController 进行子类化,因此我找到了一种使用类别来处理自动旋转的简单方法。 它不会修复您使用“更多...”视图控制器的错误,但我认为这是一种对苹果更友好的完成工作的方式(并且意味着您可以减少子类化)。

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

添加到 MyAppDelegate.h 的底部

@interface UITabBarController (MyApp)
@end

@interface UINavigationController (MyApp)
@end

添加到 MyAppDelegate.m 的底部

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

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

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
不及他 2024-08-06 20:10:26

看来我们有一个错误。 我创建了一个可重现的最小测试用例,并通过 Apple Bug Reporter 报告了它(RADAR 问题 7139857) 。

更新:从 iPhone OS 3.1 开始已修复此问题。

根本问题是:

查看已在
导航控制器堆栈不
收到
willAnimateRotationToInterfaceOrientation:持续时间:
当选项卡栏控制器的消息
“更多导航控制器”已上线
使用。

当选项卡栏项目视图控制器是基本视图控制器时,不会发生此问题。 仅当它们是导航控制器并且当“更多”导航层次结构正在使用时。

控制台消息(关于两阶段旋转动画)表明框架内的某些内容(更多导航控制器?)仍在使用两阶段动画,尽管现在建议从 iPhone OS 3.0 开始使用单阶段动画。

这可以解释为什么在这种特殊情况下不会调用 willAnimateRotationToInterfaceOrientation: 。 根据 Apple 的视图控制器文档,当响应两阶段、前/后半方向消息时,不会调用此消息。

It appears we have a bug. I have created a reproducible, minimal test case, and reported it via Apple Bug Reporter (RADAR Problem 7139857).

Update: This has been fixed as of iPhone OS 3.1.

The essential problem is:

View controllers already on a
Navigation Controller stack do not
receive
willAnimateRotationToInterfaceOrientation:duration:
messages when a Tab Bar Controller's
'More Navigation Controller' is in
use.

This problem does not occur when the tab bar item view controllers are basic view controllers. Only when they are navigation controllers and only when the "More" navigation hierarchy is in use.

The console message (regarding two-stage rotation animation) suggests that something within the framework (the More Navigation Controller?) is still using a two-stage animation, even though single stage is now recommended as of iPhone OS 3.0.

That could explain why willAnimateRotationToInterfaceOrientation: is not called in that particular case. Per Apple's view controler documentation, this message will NOT be invoked when two-stage, first/second-half orientation messages are being responded to instead.

信愁 2024-08-06 20:10:26

Victorb 的答案的稍微修改版本,允许每个视图控制器决定是否允许旋转。

这里作为更容易复制的要点 分叉

AppDelegate.h

@interface UITabBarController (MyApp)
@end

@interface UINavigationController (MyApp)
@end

AppDelegate.m

@implementation UITabBarController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    UIViewController *selectedVC = [self selectedViewController];
    if ([selectedVC respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
        return [selectedVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
    }

    //optimistic return - if you want no rotation, you have to specifically tell me!
    return YES;
}
@end

@implementation UINavigationController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    UIViewController *visibleVC = [self visibleViewController];
    if ([visibleVC respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
        return [visibleVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
    }

    //optimistic return - if you want no rotation, you have to specifically tell me!
    return YES;
}
@end

A slightly modified version of Victorb's answer which allows every single view controller to decide if it allows rotation.

Here as a gist for easier copying & forking

AppDelegate.h

@interface UITabBarController (MyApp)
@end

@interface UINavigationController (MyApp)
@end

AppDelegate.m

@implementation UITabBarController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    UIViewController *selectedVC = [self selectedViewController];
    if ([selectedVC respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
        return [selectedVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
    }

    //optimistic return - if you want no rotation, you have to specifically tell me!
    return YES;
}
@end

@implementation UINavigationController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    UIViewController *visibleVC = [self visibleViewController];
    if ([visibleVC respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
        return [visibleVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
    }

    //optimistic return - if you want no rotation, you have to specifically tell me!
    return YES;
}
@end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文