UIWindow in iOS
- 1. keyWindow
- 2. UIWindow is always portrait
- 3. Keyboard is a window
- 4. Notification
- 5. Statusbar is a window
- 6. statusBarFrame
- 7. Notification
- 8. How to create alert view
- 9. Don’t use rootViewController approach
- 10. How to add view to existing window
- 11. How the OS creates AlertView
- 12. Use rootViewController approach
- 13. UIWindow is a UIView
- 14. UIWindow in iOS 8
- 15. Reference
In this article, I ‘ll share what I know about UIWindow
keyWindow
An app can have many UIWindow. The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window.
You call makeKeyAndVisible or makeKeyWindow to make a UIWindow become the keyWindow. Note that UIWindow is hidden by default, so makeKeyAndVisible both makes a UIWindow become keyWindow and set its hidden property to NO
UIWindow is always portrait
Simply add the following code in application:didFinishLaunchingWithOptions:
1 |
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; |
The UIWindow’s coordinate system is always in portrait orientation. It applies the rotation by setting its rootViewController’s view’s transform. So starting in iOS 4, application is advised to have rootViewController. See this wonderful answer from rob After rotation UIView coordinates are swapped but UIWindow’s are not?
Keyboard is a window
Note that UIApplication has an instance method “windows”
The app’s visible and hidden windows. (read-only)
This property contains the UIWindow objects currently associated with the app. This list does not include windows created and managed by the system, such as the window used to display the status bar
You can get the keyboard window by traversing this windows array. Code extracted from SVProgressHUD. Note that when the keyboard is shown, the windows array contains another window of type UITextEffectsWindow, that is the keyboard
1 |
- (CGFloat)visibleKeyboardHeight { |
As said previously, The UIWindow’s coordinate system is always in portrait orientation. Keyboard is a window, so its frame is always (0 0; 320 480) regardless of device orientation. When you rotate the device, the system applies a rotation transform to the keyboard window
User rotates the device to portrait
1 |
<UITextEffectsWindow: 0x8e3ecc0; frame = (0 0; 320 480); opaque = NO; gestureRecognizers = <NSArray: 0x8e3f240>; layer = <UIWindowLayer: 0x8e3ee40>> |
User rotates the device to landscape
1 |
<UITextEffectsWindow: 0x8e3ecc0; frame = (0 0; 320 480); transform = [0, 1, -1, 0, -80, 80]; opaque = NO; gestureRecognizers = <NSArray: 0x8e3f240>; layer = <UIWindowLayer: 0x8e3ee40>> |
Notification
If you register for UIKeyboardWillShowNotification, you can confirm this
1 |
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *note) { |
User rotates the device to portrait
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
User rotates the device to landscape
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 0}, {162, 480}}";
So in your UIKeyboardWillShowNotification handler, you should convert it to your view coordinate system. Code extracted from Keyboard “WillShow” and “WillHide” vs. Rotation
1 |
- (void) keyboardWillShow:(NSNotification *)aNotification |
Statusbar is a window
As said previously, the windows array does not contain the statusbar window. Statusbar window is of type UIStatusBarWindow, you can get it with the following code (extracted from FLEX)
1 |
- (UIWindow *)statusWindow |
Like the keyboard window, the status bar frame is unchanged. When you rotate the device, the system applies rotation transform to the status bar
User rotates the device to portrait
1 |
<UIStatusBarWindow: 0x8f61e30; frame = (0 0; 320 480); gestureRecognizers = <NSArray: 0x8f62e40>; layer = <UIWindowLayer: 0x8f620d0>> |
User rotates the device to landscape
1 |
<UIStatusBarWindow: 0x8f61e30; frame = (0 0; 320 480); transform = [0, 1, -1, 0, 0, 0]; gestureRecognizers = <NSArray: 0x8f62e40>; layer = <UIWindowLayer: 0x8f620d0>> |
statusBarFrame
UIApplication has a property called statusBarFrame, which is always reported in portrait coordinate system.
1 |
CGRect rect = [UIApplication sharedApplication].statusBarFrame; |
User rotates the device to portrait
statusBarFrame {{0, 0}, {320, 20}} User rotates the device to landscape statusBarFrame {{300, 0}, {20, 480}}
If you merely want to get the status height, regardless of orientation, you can just get the max
1 |
float statusBarHeight = MIN([UIApplication sharedApplication].statusBarFrame.size.height, [UIApplication sharedApplication].statusBarFrame.size.width); |
Notification
When the device is rotated, the system issues UIApplicationWillChangeStatusBarFrameNotification and UIApplicationDidChangeStatusBarFrameNotification.
Let’s now handle UIApplicationDidChangeStatusBarFrameNotification to see
1 |
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarFrameNotification object:nil queue:nil usingBlock:^(NSNotification *note) { |
I don’t know why but the WillChange notification gives me the final result, while the DidChange notification give me the old result. By result, I mean the value of the statusBarFrame
I also see that no libraries use the DidChange notification
How to create alert view
To create an alertview, you can simply present an UIViewController (with smaller size). For advanced cases, you must understand the presentation context. And the way to use your alertview is a bit hard. What we always want is a simple call
1 |
[alertView show]; |
It ‘s not to say that some view can appears on top of your alert view, so they defeats your purpose as an alertview
The fancy way is to work with UIWindow. I see many libraries use one of these 2 approaches
Don’t use rootViewController approach
Some libraries like OLGhostAlertView, SVProgressHUD, WYPopoverController, MTStatusBarOverlay … don’t use rootViewController. They can create new UIWindow (MTStatusBarOverlay) or use existing UIWindow, they addSubview directly to the window, so they handle orientation by listen to UIApplicationDidChangeStatusBarOrientationNotification or UIApplicationWillChangeStatusBarFrameNotification
The handler is like this, extracted from SVProgressHUD. The idea is manually get the orientation and applies a rotation transform to their view
1 |
- (void)handleOrientationChange:(NSNotification *)note |
How to add view to existing window
Libraries have their own strategy to add view to existing window
SVProgressHUD tries to add to the front window
1 |
if(!self.overlayView.superview){ |
How the OS creates AlertView
Watch WWDC 2014 Session 228 A Look Inside Presentation Controllers, they said
Behind the scenes, the framework creates a window on your app’s behalf, but this predates iOS 8 window rotation behavior, so this window is technically still in portrait.
We then add the action sheet to that window and mimic the transform hierarchy of the presenting view to get into the right orientation.
Use rootViewController approach
Some libraries, like FLEX, SIAlertView, … create new UIWindow and assign rootViewController to that window. This way orientation is reported to the rootViewController. They then simply add view to the rootViewController.view and handle orientation right in the view controller.
Code extracted from FLEXViewExplorerViewController
1 |
- (NSUInteger)supportedInterfaceOrientations |
Since FLEX creates new Window, it must consults the app original Window for rotation capability. Note the infoPlistSupportedInterfaceOrientationsMask and viewControllerForStatusBarAndOrientationProperties, you can learn much from it
UIWindow is a UIView
Since UIWindow is a UIView subclass, you can do many things you already know with UIView. For example, FLEX override pointInside:withEvent: to intercept touch through its toolbar
1 |
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event |
UIWindow in iOS 8
In iOS 8, Apple introduce Size Classes, and “Rotation is an animated bounds change”. And UIWindow is reported in device orientation
WWDC 2014 Session 216 Building Adaptive Apps with UIKit
Trait Environments are a new protocol that are able to return their current Trait Collection, and these include Screens, Windows, View Controllers, and also Views.
All of these are able to return their current Trait Collection to you to use to determine how your interface should be laid out.
See Is [UIScreen mainScreen].bounds.size becoming orientation-dependent in iOS8?
UIScreen is now interface oriented:
- [UIScreen bounds] now interface-oriented
- [UIScreen applicationFrame] now interface-oriented
- Status bar frame notifications are interface-oriented
- Keyboard frame notifications are interface-oriented