自定义 UIWindows 在 iOS 8 中无法正确旋转

Posted

技术标签:

【中文标题】自定义 UIWindows 在 iOS 8 中无法正确旋转【英文标题】:Custom UIWindows do not rotate correctly in iOS 8 【发布时间】:2014-11-03 00:14:02 【问题描述】:

让您的应用程序进入横向模式并执行以下代码:

UIWindow *toastWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
toastWindow.hidden = NO;
toastWindow.backgroundColor = [[UIColor cyanColor] colorWithAlphaComponent:0.5f];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
    [toastWindow removeFromSuperview];
);

ios 7 中,您将在整个屏幕顶部获得一个透明的蓝色覆盖层,该覆盖层会在 5 秒后消失。在 iOS 8 中,您将获得一个覆盖一半多一点屏幕的透明蓝色覆盖层

这显然与 Apple 在 iOS 8 中所做的更改有关,其中屏幕坐标现在面向界面而不是面向设备,但在真正的 Apple 时尚中,它们似乎在横向模式和旋转中留下了无数错误。

我可以通过检查设备方向是否为横向并翻转主屏幕边界的宽度和高度来“解决”这个问题,但这似乎是一个可怕的黑客行为,当 Apple 在 iOS 9 中再次更改所有内容时,它会被打破。

CGRect frame = [[UIScreen mainScreen] bounds];

if (UIInterfaceOrientationIsLandscape([UIDevice currentDevice].orientation))

    frame.size.width = frame.size.height;
    frame.size.height = [[UIScreen mainScreen] bounds].size.width;


UIWindow *toastWindow = [[UIWindow alloc] initWithFrame:frame];
toastWindow.hidden = NO;
toastWindow.backgroundColor = [[UIColor cyanColor] colorWithAlphaComponent:0.5f];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
    [toastWindow removeFromSuperview];
);

有没有人遇到过这个问题并找到了更好、更简单的解决方案?

编辑:我知道我可以只使用 UIView 并将其添加到关键窗口,但我想在状态栏顶部放一些东西。

【问题讨论】:

文档中没有说明使用多个 UIWindows 是错误的。自 iOS 2 以来人们一直在使用它们。 我在你的代码中看不到任何脆弱的东西。你在说什么?如果您的抱怨是窗口没有自动布局......那么答案是使用视图而不是窗口。很简单。 这是 Apple 对 windows 的评价:“大多数 iOS 应用程序在其生命周期内只创建和使用一个窗口。[...] 但是,如果应用程序支持使用外部显示器进行视频输出, 它可以创建一个附加窗口来在该外部显示器上显示内容。所有其他窗口通常由系统创建”。换句话说,如果你超出了 Apple 推荐的 windows 使用范围,那么你就只能靠自己了,Apple 不会做任何事情来简化多个窗口的使用——它们也不会将自己排除在向后不兼容的更改之外。 说真的,我和 Reid 在一起,你从哪里得到这些信息? UIWindowLevel 自 iOS 2.0 和 iOS 7 以来,UIKit 中就提供了堆栈级别,使用此 API 的理由从未如此美好! @AbhiBeckert 说“大多数 iOS 应用程序只创建和使用一个窗口...”不是建议,只是事实陈述。 iOS 应用程序长期以来一直在使用多个 Windows,Apple 打破这一点应该被认为是他们的失败,而不是我们的失败。 【参考方案1】:

由于另一个原因,您的“修复”不是很好,因为它实际上并没有旋转窗口,以便文本和其他子视图以适当的方向显示。换句话说,如果你想用其他子视图来增强窗口,它们的方向就会不正确。

...

在 iOS8 中,您需要设置窗口的 rootViewController,并且该 rootViewController 需要从 'shouldAutoRotate' 和 'supportedInterfaceOrientations' 返回适当的值。更多关于此的内容:https://devforums.apple.com/message/1050398#1050398

如果您的窗口没有 rootViewController,您实际上是在告诉框架该窗口不应该自动旋转。在 iOS7 中,这并没有什么不同,因为框架并没有为你做这些工作。在 iOS8 中,框架正在处理旋转,当它限制窗口的边界时,它认为它正在执行您所请求的操作(通过使用 nil rootViewController)。

试试这个:

@interface MyRootViewController : UIViewController
@end

@implementation MyRootViewController

- (UIInterfaceOrientationMask)supportedInterfaceOrientations

    return UIInterfaceOrientationMaskAll;


- (BOOL)shouldAutorotate

    return YES;


@end

现在,在实例化之后将 rootViewController 添加到您的窗口中:

UIWindow *toastWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
toastWindow.rootViewController = [[MyRootViewController alloc]init];

【讨论】:

从技术上讲,您不需要覆盖这些方法。 “shouldAutorotate”方法在 iOS 8 中已弃用,默认情况下 supportedInterfaceOrientations 返回 UIInterfaceOrientationMaskAll。你是对的。我只需要创建一个“虚拟” UIViewController,一切几乎都回到了 iOS 7 中的方式。有一个主要区别,那就是你的 UIWindow 的边界必须与设备边界匹配,而在 iOS 中它不需要8. @ReidMain 关于边界的好提示,对我来说就是这样。谢谢。 shouldRotate:supportedInterfaceOrientations 添加到我的UIWindow 子类中的rootViewController 修复了iOS 8 上的问题。非常感谢! @ReidMain 你是如何让虚拟控制器工作的?对我来说,当我实施它时,我的警报会禁用触摸。你还有什么事情要做吗? @CoDEFRo 回顾我的代码,我没有做任何特别的事情。刚刚创建了一个 UIWindow,添加了一个虚拟视图控制器作为根视图控制器,然后将我的自定义 UIView 添加到窗口中。我假设您能够看到您的警报,但无法对其采取行动?【参考方案2】:

我相信你必须转换UICoordinateSpace

在 iPhone 6 Plus 上,应用程序现在可以横向启动,这搞砸了我正在开发的应用程序,因为它仅在大多数应用程序中支持纵向,除了一个屏幕(这意味着它需要在列表)。

修复此问题的代码如下:

self.window = [[UIWindow alloc] initWithFrame:[self screenBounds]];

并使用以下代码计算边界:

- (CGRect)screenBounds

    CGRect bounds = [UIScreen mainScreen].bounds;
    if ([[UIScreen mainScreen] respondsToSelector:@selector(fixedCoordinateSpace)]) 
        id<UICoordinateSpace> currentCoordSpace = [[UIScreen mainScreen] coordinateSpace];
        id<UICoordinateSpace> portraitCoordSpace = [[UIScreen mainScreen] fixedCoordinateSpace];
        bounds = [portraitCoordSpace convertRect:[[UIScreen mainScreen] bounds] fromCoordinateSpace:currentCoordSpace];
    
    return bounds;

希望这将引导您朝着正确的方向前进。

【讨论】:

是的,这似乎成功了。它仍然没有解决添加到窗口的任何视图也没有旋转的问题,但这可能是窗口没有根视图控制器的问题。也许我应该将我的视图封装在一个控制器中,看看它是否有效。 是的,如果你在 UIWindow 上设置了 rootViewController,你可以通过 UIViewController 上的常用方法来控制方向。 虽然这可行,但我仍然不能 100% 确定这是 Apple 的意图。如果我使用您的代码并使用 Xcode 6 中的“调试视图层次结构”工具,主窗口的视图是横向的,而新窗口仍然是纵向的。虽然这是有道理的,因为我们转换了坐标空间,但我不明白主窗口如何看起来像在横向模式下。苹果肯定在做一些不同的事情。 看起来设置根视图控制器也不起作用。窗口仍然没有被认为是旋转的,所以它里面的视图控制器的视图大小不合适。 这看起来确实很奇怪。如果不使用上述代码进行调试,是否会显示为横向?【参考方案3】:

在 iOS 7 及更早版本中,UIWindow 的坐标系没有旋转。在 iOS 8 中确实如此。我猜[[UIScreen mainScreen] bounds] 提供的框架不考虑旋转,这可能会导致您所看到的问题。

您可以从 appDelegate 的当前关键窗口中获取帧,而不是从 UIScreen 获取帧。

但是,您似乎并不真正需要UIWindow 提供的功能。我想回应其他人的建议,即您为此目的使用UIViewUIViewUIWindow 更通用,应该是首选。

【讨论】:

我实际上已经尝试过使用关键窗口框架,您也遇到了完全相同的问题。我也不想使用 UIView,因为在这种情况下我想在状态栏上放一些东西。【参考方案4】:

最好的办法是使用视图而不是窗口:

大多数 iOS 应用程序在其生命周期中只创建和使用一个窗口。此窗口跨越整个主屏幕 [...]。但是,如果应用程序支持使用外部显示器进行视频输出,它可以创建一个额外的窗口来在该外部显示器上显示内容。所有其他窗口通常由系统创建

但是如果你认为你有充分的理由创建多个窗口,我建议你创建一个 NSWindow 的子类来自动处理大小。

还要注意,Windows 使用大量 RAM,尤其是在 3x 视网膜屏幕上。拥有多个内存会减少应用程序的其余部分在收到内存不足警告并最终被终止之前可以使用的内存量。

【讨论】:

我过去曾使用 windows 来处理类似的事情。在这种情况下,我想要状态栏顶部的内容。 如果需要UIWindow,为什么应该首选UIView? (我在问答案的发布者)在某些情况下,人们可能想要使用第二个或第三个UIWindow,正如 Reid 指出的那样,他希望一个人坐在UIWindowLevelStatusBar。此外,如果想要对UIStatusBarStyle 进行任何类型的控制,将UIView 作为子视图添加到 UIWindow 并不是很好 - 处理状态栏的任何更改都会变得非常混乱。 Abhi,您能否发布您对 UIWindow 大量内存使用的评论的来源? @runmad 一个窗口的主要目的是存储被绘制到屏幕上的像素。在 Mac OS X 上,他们经常将此内存写入 HDD/SSD 以减少整体内存负载。但由于 iOS 设备使用 NAND 闪存而不是 HDD/SSD 内存,并且 NAND 闪存不能很好地处理频繁写入,因此 iOS 必须始终将所有窗口保留在 RAM 中。这在 iOS 的任何地方都没有记录,但在 OS X 上有记录,如果内存负载在具有 12GB 内存的 Mac 上是一个问题,那么在 iOS 上情况会更糟,这就是我对 iOS 应用程序有一个窗口和 Mac 的理解应用有很多窗口。 例如,OS X Dock 为每个应用程序图标提供了一个单独的窗口。但是对于 iOS,Apple 竭尽全力改进视图系统,以消除对多个窗口的需求。我能想到的唯一合理的理由是减少 RAM 的使用。

以上是关于自定义 UIWindows 在 iOS 8 中无法正确旋转的主要内容,如果未能解决你的问题,请参考以下文章

iOS 8自定义键盘:更改高度

为什么iOS 8上的SceneKit无法编译我的自定义着色器?

iOS 8 自定义键盘:更改高度而不发出警告“无法同时满足约束...”

自定义键盘应用程序扩展 iOS 8 上的 Siri

使用多个不都支持横向模式的 UIWindows 时出现问题

更改自定义 UIButton 的 TitleColor (iOS 8 + swift)