16

Get your application into landscape mode and execute the following code:

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

In iOS 7 you will get a transparent blue overlay on top of the entire screen that disappears after 5 seconds. In iOS 8 you will get a transparent blue overlay that covers a little over half the screen

device screenshot

This obviously has something to do with the change Apple did in iOS 8 where the screen coordinates are now interface oriented instead of device oriented but in true Apple fashion they seem to have left a myriad of bugs in landscape mode and rotation.

I can "fix" this by checking if the device orientation is landscape and flip the width and height of the main screen bounds but that seems like a horrible hack that is going to break when Apple changes everything again in 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];
});

Has anyone been experiencing this problem and come across a better, less brittle solution?

EDIT: I know I could just use a UIView and add it to the key window but I would like to put something on top of the status bar.

Reid Main
  • 3,394
  • 3
  • 25
  • 42
  • 1
    There is nothing in the documentation that says using multiple UIWindows is wrong. People have been using them since iOS 2. – Reid Main Nov 03 '14 at 00:35
  • I don't see anything in your code that is brittle. What are you talking about? If your complaint is that there is no auto layout for windows... then the answer is to use views instead of windows. Simple. – Abhi Beckert Nov 03 '14 at 01:12
  • This is what Apple says about windows: "Most iOS applications create and use only one window during their lifetime. [...] However, if an application supports the use of an external display for video out, it can create an additional window to display content on that external display. All other windows are typically created by the system". In other words, if you step outside Apple's recommended use of windows, then you're on your own and Apple is not going to do anything to make using multiple windows easy — nor will they shut themselves out of backwards incompatible changes. – Abhi Beckert Nov 03 '14 at 01:14
  • It is brittle because I am making the assumption that just flipping the width and height will be sufficient. In any upcoming iOS release Apple could change that and my code would break. – Reid Main Nov 03 '14 at 01:23
  • Just like the assumption I made about applying a transform to the window worked up until iOS 7. – Reid Main Nov 03 '14 at 01:29
  • If you can provide me a link to some documentation that says you are never suppose to create another UIWindow I will concede defeat but I do not believe that exists. – Reid Main Nov 03 '14 at 02:22
  • 1
    Seriously, I'm with Reid, where are you getting this information from? `UIWindowLevel` stacking level has been available in UIKit since iOS 2.0 and with iOS 7, the reason to use this API has never been better! – runmad Nov 03 '14 at 02:50
  • 1
    @AbhiBeckert Saying "Most iOS applications create and use only one window..." is *not* a recommendation, merely a statement of fact. iOS Applications have been using multiple Windows for a long time, and Apple breaking this should be recognised as a failure on their part, not ours. – Chris Hatton Dec 02 '16 at 01:07

4 Answers4

28

Your 'fix' isn't great for another reason, in that it doesn't actually rotate the window so that text and other subviews appear with the appropriate orientation. In other words, if you ever wanted to enhance the window with other subviews, they would be oriented incorrectly.

...

In iOS8, you need to set the rootViewController of your window, and that rootViewController needs to return the appropriate values from 'shouldAutoRotate' and 'supportedInterfaceOrientations'. There is some more about this at: https://devforums.apple.com/message/1050398#1050398

If you don't have a rootViewController for your window, you are effectively telling the framework that the window should never autoRotate. In iOS7 this didn't make a difference, since the framework wasn't doing that work for you anyway. In iOS8, the framework is handling the rotations, and it thinks it is doing what you requested (by having a nil rootViewController) when it restricts the bounds of your window.

Try this:

@interface MyRootViewController : UIViewController
@end

@implementation MyRootViewController

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskAll;
}

- (BOOL)shouldAutorotate
{
    return YES;
}

@end

Now, add that rootViewController to your window after it is instantiated:

UIWindow *toastWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
toastWindow.rootViewController = [[MyRootViewController alloc]init];
Daniel
  • 23,129
  • 12
  • 109
  • 154
Rich Waters
  • 1,567
  • 16
  • 15
  • 2
    Technically you don't need to override those methods. The "shouldAutorotate" methods are deprecated in iOS 8 and by default `supportedInterfaceOrientations` returns `UIInterfaceOrientationMaskAll`. You are correct though. I just needed to create a "dummy" UIViewController and everything almost went back to the way it was in iOS 7. There is one major difference and that is your UIWindow's bounds must match the device bounds whereas it didn't need to that in iOS 8. – Reid Main Nov 24 '14 at 00:31
  • @ReidMain Good tip on the bounds, that was it for me. Thanks. – Matt Mc Feb 21 '15 at 02:46
  • 1
    Adding ```shouldRotate:``` and ```supportedInterfaceOrientations``` to the rootViewController within my UIWindow subclass fixes issue on iOS 8. Many thanks! – HackaZach Mar 20 '15 at 06:18
  • @ReidMain How did you get the dummy controller to work? For me when Implement it, the touches are disabled on my alert. Is there something else you have to do? – Ser Pounce May 14 '15 at 01:20
  • @CoDEFRo Looking back at my code I didn't do anything special. Just created a UIWindow, added a dummy view controller as the root view controller and then added my custom UIView to the window. I assume you are able to see your alert and just not be able to act on it? – Reid Main May 23 '15 at 17:33
  • @CoDEFRo did you make sure you size the window correctly? I made mine the bounds of the device. – Reid Main May 23 '15 at 17:35
  • @ReidMain Yeah the window is the bounds, not sure what the heck I'm doing, thanks for the response though. – Ser Pounce May 23 '15 at 19:28
3

I believe you have to convert the UICoordinateSpace.

On iPhone 6 Plus apps can now launch already in landscape orientation, which messed up an application I am working on since it only supports portrait orientation throughout most of the app, except one screen (meaning it needed to support landscape orientations in the plist).

The code that fixed this was as follows:

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

and calculating the bounds with the following code:

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

Hopefully this will lead you in the right direction.

runmad
  • 14,846
  • 9
  • 99
  • 140
  • Yup that seemed to do the trick. It still doesn't solve the problem where any views added to the window aren't rotate as well but that is maybe an issue with the window not having a root view controller. Perhaps I should wrap my view up in a controller and see if that works. – Reid Main Nov 03 '14 at 03:37
  • Yeah, if you set a rootViewController on the UIWindow you can control orientations through the usual methods on UIViewController. – runmad Nov 03 '14 at 04:02
  • While this works I'm still not 100% sure this is what Apple intends. If I use your code and use the "Debug View Hierarchy" tool in Xcode 6 the main window's view is in landscape while the new window is still in portrait. While this makes sense because we converted the coordinate space I don't understand how the main window is able look like it is in landscape mode. Apple must be doing something different to it. – Reid Main Nov 03 '14 at 16:31
  • 1
    It looks like setting the root view controller doesn't work either. The window is still not considered rotated so the view controller's view inside it is not properly sized. – Reid Main Nov 03 '14 at 16:47
  • That does seem really weird. And if you debug without the above code, does it display it as landscape? – runmad Nov 03 '14 at 20:20
  • No if I don't use your code everything is still screwed up. In iOS 7 you can see the code I used to reposition windows here https://github.com/reidmain/Xcode-6-Project-Templates/blob/master/iOS%20Application.xctemplate/FDVersionWindow.m and now it seems that in iOS 8 because I don't have access to nor understand the transform that is being applied to the window I can't mimic Apple's functionality. – Reid Main Nov 04 '14 at 04:23
1

In iOS 7 and earlier, UIWindow's coordinate system did not rotate. In iOS 8 it does. I am guessing the frame supplied from [[UIScreen mainScreen] bounds] does not account for rotation, which could cause issues like what you're seeing.

Instead of getting the frame from UIScreen, you could grab the frame from the appDelegate's current key window.

However, it does not appear you really need functionality supplied by UIWindow. I'd like to echo others' recommendation that you use a UIView for this purpose. UIView is more generic than UIWindow, and should be preferred.

Nick
  • 2,361
  • 16
  • 27
  • I have actually tried using the key window frame and you have the exact same problem. Also I don't want to use UIView's because in this case I would like to put something on top of the status bar. – Reid Main Nov 03 '14 at 02:23
0

The best thing to do is use views instead of windows:

Most iOS applications create and use only one window during their lifetime. This window spans the entire main screen [...]. However, if an application supports the use of an external display for video out, it can create an additional window to display content on that external display. All other windows are typically created by the system

But if you think you've got a valid reason to create more than one window, I suggest you create a subclass of NSWindow that handles sizes automatically.

Also note that windows use a lot of RAM, especially on 3x retina screens. Having more than one of them is going to reduce the amount of memory the rest of your application can use before receiving low memory warnings and eventually being killed.

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
  • 1
    I have used windows for stuff like this in the past. In this scenario I want something that goes on top of the status bar. – Reid Main Nov 03 '14 at 01:22
  • Why should `UIView` be preferred if a `UIWindow` is needed? (I'm asking the poster of the answer) There are several cases where one might want to use a second or third `UIWindow`, as Reid points out he wants one to sit at `UIWindowLevelStatusBar`. Also, adding a `UIView` as a subview to UIWindow isn't great when it comes to wanting any type of control over the `UIStatusBarStyle` - it'll get really messy quick to handle any changes to the status bar. – runmad Nov 03 '14 at 02:48
  • Abhi, can you post your source for your comment on `UIWindow`'s substantial memory usage? – runmad Nov 03 '14 at 02:55
  • @runmad a window's primary purpose is to store the pixels that are be drawn to the screen. On Mac OS X, they often write this memory to the HDD/SSD to reduce overall memory load. But since iOS devices use NAND flash instead of HDD/SSD memory, and NAND flash does not handle frequent writes well, iOS has to keep all windows in RAM all the time. This is not documented anywhere for iOS but it is documented for OS X and if memory load is an issue on a mac with 12GB of memory, then it's even worse on iOS and this is my understanding of why iOS apps have one window, and Mac Apps have many windows. – Abhi Beckert Nov 03 '14 at 10:07
  • For example the OS X dock has a separate window for every app icon. But for iOS Apple went to extensive lengths improving the view system to eliminate the need for having more than one window. The only sensible reason to do this, that I can think of, is to reduce RAM usage. – Abhi Beckert Nov 03 '14 at 10:08
  • Sorry I don't have a source, I'm just basing it on my own understanding of how the windowing system works on OS X. I don't know if it's the same for iOS, Apple doesn't discuss how windows work in detail because developers generally are encouraged not to use windows much. – Abhi Beckert Nov 03 '14 at 10:09
  • I disagree with you. It's documented by Apple and available since iOS 2.0. I don't see anywhere where it's discouraged by Apple due to memory constraints, etc. I also find that there's a great deal of documentation for how UIWindow's work and if you look at the header file there's lots more, with many methods exposed and explained. – runmad Nov 03 '14 at 13:02