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

16

将您的应用程序切换到横屏模式并执行以下代码:

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并将其添加到关键窗口,但我想将某些内容放在状态栏上方。


1
文档中没有说明使用多个UIWindows是错误的。自iOS 2以来,人们一直在使用它们。 - Reid Main
这是苹果关于窗口的说法:“大多数iOS应用程序在其生命周期中仅创建和使用一个窗口。[...]但是,如果应用程序支持使用外部显示器进行视频输出,则可以创建一个附加窗口以在该外部显示器上显示内容。所有其他窗口通常由系统创建。”换句话说,如果您超出了苹果建议使用窗口的范围,那么您就自己承担风险,苹果不会采取任何措施使使用多个窗口变得容易 - 他们也不会将自己排除在向后不兼容的更改之外。 - Abhi Beckert
就像我对在iOS 7之前对窗口应用变换的假设一样,只是出现了问题。 - Reid Main
1
说真的,我同意Reid的观点,你从哪里获取到这些信息的? UIWindowLevel 堆叠级别自iOS 2.0以来就已经在UIKit中提供,并且随着iOS 7的出现,使用这个API的原因变得更加充分! - runmad
1
@AbhiBeckert说“大多数iOS应用程序仅创建和使用一个窗口...”并不是建议,而只是事实陈述。iOS应用程序长期以来一直在使用多个窗口,而苹果公司打破这一点应该被认为是他们的失败,而不是我们的失败。 - Chris Hatton
显示剩余3条评论
4个回答

28

你的修复方法还存在另一个问题,它并没有实际上旋转窗口,以至于文本和其他子视图显示出来的方向不正确。换句话说,如果你想增强窗口并添加其他子视图,它们将被错误地定位。

...

iOS 8中,你需要设置窗口的根视图控制器,并且该根视图控制器需要从'shouldAutoRotate'和'supportedInterfaceOrientations'返回适当的值。这方面还有更多信息可在此处查看:https://devforums.apple.com/message/1050398#1050398

如果你的窗口没有根视图控制器,那么实际上是在告诉框架窗口永远不会自动旋转。在iOS 7中,这并没有什么影响,因为框架并没有为你完成这项工作。在iOS 8中,框架正在处理旋转,它认为它正在按照你的要求(通过具有nil根视图控制器)执行任务,当它限制窗口的边界时。

尝试这个:

@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];

2
从技术上讲,您不需要覆盖那些方法。在iOS 8中,“shouldAutorotate”方法已被弃用,默认情况下supportedInterfaceOrientations返回UIInterfaceOrientationMaskAll。不过您是正确的。我只需要创建一个“虚拟”的UIViewController,几乎可以回到iOS 7的状态。有一个主要区别,即您的UIWindow的边界必须与设备边界匹配,而在iOS 8中不需要这样做。 - Reid Main
1
在我的UIWindow子类中添加shouldRotate:supportedInterfaceOrientations到rootViewController可以解决iOS 8上的问题。非常感谢! - HackaZach
@ReidMain,你是怎么让虚拟控制器工作的?我实现后,我的警告框上的触摸功能被禁用了。还需要做些什么吗? - Ser Pounce
回顾我的代码,我并没有做什么特别的事情。只是创建了一个UIWindow,将虚拟视图控制器添加为根视图控制器,然后将我的自定义UIView添加到窗口中。我猜你能看到你的警报,只是不能对它采取行动? - Reid Main
@ReidMain 是的,窗口就是边界,我也不确定自己在干什么,不过还是谢谢你的回复。 - Ser Pounce
显示剩余2条评论

3

我相信你需要转换 UICoordinateSpace

在 iPhone 6 Plus 上,应用程序现在可以在横向方向上启动,这破坏了我正在开发的应用程序,因为在大部分应用中它只支持纵向方向,除了一个屏幕(这意味着需要在 plist 中支持横向方向)。

解决这个问题的代码如下所示:

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;
}

希望这能为您指明正确的方向。


是的,这似乎达到了预期的效果。但它仍然没有解决窗口中添加的任何视图旋转的问题,这可能与窗口没有根视图控制器有关。也许我应该将我的视图封装在一个控制器中,看看那样是否可以解决问题。 - Reid Main
是的,如果您在UIWindow上设置了rootViewController,则可以通过UIViewController上的通常方法来控制方向。 - runmad
虽然这个方法可以工作,但我仍然不能完全确定这是否符合苹果的意图。如果我使用您的代码并在Xcode 6中使用“Debug View Hierarchy”工具,则主窗口的视图为横向,而新窗口仍为纵向。虽然这是有道理的,因为我们转换了坐标空间,但我不明白主窗口如何能看起来像是横向模式。苹果一定对它做了某些不同的处理。 - Reid Main
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Reid Main
这确实很奇怪。如果你在没有上述代码的情况下进行调试,它是否以横向显示? - runmad
如果我不使用你的代码,一切仍然混乱不堪。在iOS 7中,您可以在此处查看我用于重新定位窗口的代码https://github.com/reidmain/Xcode-6-Project-Templates/blob/master/iOS%20Application.xctemplate/FDVersionWindow.m,现在似乎在iOS 8中,因为我无法访问也无法理解应用于窗口的变换,我无法模仿苹果的功能。 - Reid Main

1
在iOS 7及之前,UIWindow的坐标系统不会旋转。在iOS 8中会旋转。我猜测从[[UIScreen mainScreen] bounds]获取的框架没有考虑旋转,这可能会导致出现您所看到的问题。
您可以从appDelegate的当前关键窗口获取框架,而不是从UIScreen获取框架。
但是,似乎您真的不需要UIWindow提供的功能。我想重申其他人的建议,您应该使用UIView来实现此目的。UIViewUIWindow更通用,应该被优先选择。

我实际上已经尝试使用关键窗口框架,但是你遇到了完全相同的问题。此外,我不想使用UIView,因为在这种情况下,我想把一些东西放在状态栏的顶部。 - Reid Main

0

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

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

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

此外,请注意,窗口使用大量RAM,特别是在3倍视网膜屏幕上。拥有多个窗口将减少其余应用程序可以使用的内存量,直到接收到低内存警告并最终被杀死。


1
我以前曾经用过Windows来做这样的事情。在这种情况下,我想要一个可以放在状态栏顶部的东西。 - Reid Main
如果需要UIWindow,为什么应该优先选择UIView?(我在问答案的发布者)有几种情况下,人们可能想要使用第二个或第三个UIWindow,正如Reid指出他想要一个坐在UIWindowLevelStatusBar上。此外,将UIView添加为UIWindow的子视图时,当涉及到想要对UIStatusBarStyle进行任何类型的控制时,这并不是很好 - 处理状态栏的任何更改会变得非常混乱。 - runmad
例如,OS X dock 为每个应用程序图标都有一个单独的窗口。但是对于 iOS,苹果公司花费了大量精力改进视图系统,以消除需要多个窗口的需求。我能想到的唯一合理的原因是减少 RAM 的使用。 - Abhi Beckert
抱歉,我没有源代码,只是基于我对 OS X 窗口系统工作方式的理解。我不知道 iOS 是否相同,因为 Apple 没有详细讨论窗口的工作原理,因为通常鼓励开发人员不要过多使用窗口。 - Abhi Beckert
我不同意你的观点。这已经被苹果文档记录并自iOS 2.0以来可用。我没有看到任何地方因为内存限制等原因而被苹果不鼓励使用。我还发现有很多关于UIWindow如何工作的文档,如果你查看头文件,会发现有很多方法被公开和解释。 - runmad
显示剩余2条评论

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