Пользовательские UIWindows не вращаются правильно в iOS 8

Получите ваше приложение в альбомном режиме и выполните следующий код:

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 вы получите прозрачный синий накладной, который покрывает чуть более половины экрана

device screenshot

Это, очевидно, имеет какое-то отношение к изменению, которое 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];
});

Кто-нибудь сталкивался с этой проблемой и сталкивался с лучшим, менее хрупким решением?

EDIT: Я знаю, что могу просто использовать UIView и добавить его в ключевое окно, но я бы хотел поставить что-то поверх строки состояния.

Ответ 1

Ваше "исправление" не подходит по другой причине, поскольку оно фактически не поворачивает окно, чтобы текст и другие подпункты отображались с соответствующей ориентацией. Другими словами, если вы когда-либо хотели улучшить окно с другими областями, они были бы неправильно ориентированы.

...

В iOS8 вам нужно установить rootViewController вашего окна, и rootViewController должен вернуть соответствующие значения из 'shouldAutoRotate' и 'supportedInterfaceOrientations'. Об этом есть еще несколько слов: https://devforums.apple.com/message/1050398#1050398

Если у вас нет rootViewController для вашего окна, вы эффективно сообщаете фреймворку, что в окне никогда не должно быть autoRotate. В 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];

Ответ 2

Я считаю, что вам нужно преобразовать 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;
}

Надеюсь, это приведет вас в правильном направлении.

Ответ 3

В iOS 7 и ранее система координат UIWindow не вращалась. В iOS 8 это так. Я предполагаю, что кадр, поставляемый с [[UIScreen mainScreen] bounds], не учитывает поворот, что может вызвать проблемы, подобные тем, что вы видите.

Вместо того, чтобы получать кадр из UIScreen, вы можете захватить фрейм из окна текущего ключа приложения appDelegate.

Однако, похоже, вам не нужны функциональные возможности, предоставляемые UIWindow. Я хотел бы повторить рекомендацию других, чтобы вы использовали UIView для этой цели. UIView более общий, чем UIWindow, и должен быть предпочтительным.

Ответ 4

Самое лучшее, что нужно сделать, это использовать представления вместо окон:

Большинство приложений iOS создают и используют только одно окно во время их жизни. Это окно охватывает весь основной экран [...]. Однако, если приложение поддерживает использование внешнего дисплея для видеовызова, оно может создать дополнительное окно для отображения содержимого на этом внешнем дисплее. Все остальные окна обычно создаются системой

Но если вы считаете, что у вас есть веская причина для создания нескольких окон, я предлагаю создать подкласс NSWindow, который автоматически обрабатывает размеры.

Также обратите внимание, что в Windows используется много оперативной памяти, особенно на 3х сетчатых экранах. Если более чем один из них собирается уменьшить объем памяти, остальная часть вашего приложения может использовать, прежде чем получать предупреждения о низкой памяти и в конечном итоге быть убитыми.