iOS Landscape Orientation handling behind the scenes
Asked Answered
B

2

6

Can anyone give me a detailed explanation of why iOS is handling it's landscape orientation the way it does in regards to frame and transformation?

What I'm talking about is this following behavior displayed by logging the views description in the various View Lifecycle methods:

viewDidLoad: "UIView: 0x1edb5ba0; frame = (0 0; 568 320); autoresize = W+H; layer = <CALayer: 0x1edb5c50>"

viewWillAppear: "UIView: 0x1edb5ba0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x1edb5c50>"

viewDidAppear: "UIView: 0x1edb5ba0; frame = (0 0; 320 568); transform = [0, 1, -1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x1edb5c50>"

The repercussions of this are subtle, but very intriguing.

For example, I launch my app with Initial Interface Orientation, and Supported Interface Orientations both set to "Landscape (right home button)"

I then display my rootViewController like this:

self.viewController = [[MyViewController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = self.viewController;

This .xib has it's orientation set to Landscape and it's frame set to 0, 0, 568, 320. It displays correctly, and I can touch all points on the screen.

The issue then comes when I present a subview like so:

SomeView *someView = [[SomeView alloc] initWithFrame:self.view.frame];
[self.view addSubview:someView];

At this point in time, self.view.frame is reported as (0 0; 320 568) and self.view.transform is reported as transform = [0, 1, -1, 0, 0, 0]. Best case scenario, the end result is that I'm only able to touch the left 320px of the view I've just displayed, worst case is that the view's layout is butchered.

Thanks to various SO questions, I've learned that the proper way to do this is as follows:

SomeView *someView = [[SomeView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:someView]

What I haven't learned is why, and I'm very curious about that.

What I'm even more curious about is why the view is manipulated the way it is during instantiation and display.

It's been quite awhile since I've tangled with a landscape only application, but for some reason I think the current implementation of this differs from earlier versions of iOS, is that correct?

Buonarroti answered 29/4, 2013 at 16:24 Comment(0)
F
0

According to apple, by default, an app supports both portrait and landscape orientations. When the orientation of an iOS–based device changes, the system sends out a UIDeviceOrientationDidChangeNotification notification to let any interested parties know that the change occurred. By default, the UIKit framework listens for this notification and uses it to update your interface orientation automatically. This means that, with only a few exceptions, you should not need to handle this notification at all.

The reason why you have to give bounds instead of frame is simple. Once the app is in landscape its width and height are interchanged when compared to portrait view. So this leads to the corrupted behaviour. But when giving the bounds, it takes into consideration the orientation of the view and takes height and width accordingly.

When you have view controllers very near the top of the view hierarchy (or indeed at the top) you can find that you get this 'swapping' effect of the width and height. The swapping usually manifests itself on the frame, but not on the bounds of the view. This is because bounds is effectively some transformations applied to the frame -- and sometimes these transformations include a 90 degree rotation (due to device being in landscape mode). Note that the exact timing of when you check the frame property can be important too. If you're checking the property after the view loaded but before it has appeared on-screen, you can get the 'wrong' result.

Frenetic answered 15/5, 2013 at 8:19 Comment(0)
R
0

Great answer from prince. I would just like to add a quote from the docs regarding the frame property of UIView:

Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.

The bounds property, on the other hand, is expressed in the view's own coordinate system, so it never changes, no matter which transform you set.

Update: regarding your last paragraph, what makes you think this behavior is strange or has changed? The only way iOS can rotate your view hierarchy is by changing view transforms. So if you want all your views in different view controllers to be oriented the same way, you should use some kind of navigation view controller, or present your controllers modally. That way, each controller will rotate its view according to the same rules and you won't have to manage inter-view transforms manually.

In the early days you could see the code like this one:

UIViewController *ctrl = ...;
[window addSubview.ctrl.view];

This was before window had the rootViewController property, and people tended to abuse this for their own views, which would cause surprising results when device rotated. If you're saying that you don't see this code anymore, that's definitely a change, and a change for the better because that code was broken. So people simply learned to write code that works correctly in the presence of orientation changes.

If you have a game with all its UI rendered by the game's engine, you only need one (root) view controller. In this case you don't need to worry about transforms at all. Just query for the bounds of the glView and adjust your glViewport and in-game elements accordingly. Since glView will have different bounds of different devices, your game will scale gracefully on all of them as long as you don't make any assumptions about actual screen size and your view's current orientation.

Regularize answered 15/5, 2013 at 10:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.