UIViewController returns invalid frame?
There are a couple of things that you don't understand.
First, the system sends you viewDidLoad
immediately after loading your nib. It hasn't even added the view to the view hierarchy yet. So it hasn't resized your view based on the device's rotation either.
Second, a view's frame is in its superview's coordinate space. If this is your root view, its superview will be the UIWindow
(once the system actually adds your view to the view hierarchy). The UIWindow
handles rotation by setting the transform of its subview. This mean that the view's frame will not necessarily be what you expect.
Here's the view hierarchy in portrait orientation:
(lldb) po [[UIApp keyWindow] recursiveDescription]
(id) $1 = 0x09532dc0 >
| >
and here's the view hierarchy in landscape-left orientation:
(lldb) po [[UIApp keyWindow] recursiveDescription]
(id) $2 = 0x09635e70 >
| >
Notice that in landscape orientation, the frame size is 748 x 1024, not 1024 x 748.
What you probably want to look at, if this is your root view, is the view's bounds:
(lldb) p (CGRect)[0x9634ee0 bounds]
(CGRect) $3 = {
(CGPoint) origin = {
(CGFloat) x = 0
(CGFloat) y = 0
}
(CGSize) size = {
(CGFloat) width = 1024
(CGFloat) height = 748
}
}
Presumably you want to know when the view's transform, frame, and bounds get updated. If the interface is in a landscape orientation when your view controller loads its view, you will receive messages in this order:
{{0, 0}, {768, 1004}} viewDidLoad
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} viewWillAppear:
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {768, 1004}} willRotateToInterfaceOrientation:duration:
{{0, 0}, {1024, 748}} viewWillLayoutSubviews
{{0, 0}, {1024, 748}} layoutSubviews
{{0, 0}, {1024, 748}} viewDidLayoutSubviews
{{0, 0}, {1024, 748}} willAnimateRotationToInterfaceOrientation:duration:
{{0, 0}, {1024, 748}} shouldAutorotateToInterfaceOrientation:
{{0, 0}, {1024, 748}} viewDidAppear:
You can see that your view's bounds change after you receive willRotateToInterfaceOrientation:duration:
and before you receive viewWillLayoutSubviews
.
The viewWillLayoutSubviews
and viewDidLayoutSubviews
methods are new to iOS 5.0.
The layoutSubviews
message is sent to the view, not the view controller, so you will need to create a custom UIView
subclass if you want to use it.
Unable to set frame correctly before viewDidAppear
viewDidLoad is called when the class is loaded however no ui elements have been initialised and therefore any attempt to reference them will be overwritten or unavaliable during the initialisation process which happens between the viewDidLoad and viewDidAppear calls. Once all ui element have been initalised and drawn viewDidAppear is called.
viewDidLoad -
Called after the controller's view is loaded into memory
At this point the view isn't within the view hierarchy.
viewWillAppear - Notifies the view controller that its view is about to be added to a view hierarchy.
Again, the view is yet to be added to the view hierarchy.
viewDidAppear - Notifies the view controller that its view was added to a view hierarchy.
Only then is the view added to the view hierarchy.
Update
The viewDidLayoutSubviews
is the most appropriate place to modify the UI before it actually appears on the screen.
viewDidLayoutSubviews - Notifies the view controller that its view just laid out its subviews.
UIKeyboard adopts an invalid frame
After 3 months, I finally figured it out (I've been doing other stuff, FYI).
The short answer
Make sure you're not returning crap from any rotation method, otherwise keyboard goes loco.
The long version
Discovered by using the application Reveal, that draws a 3d decomposition of each layer in the app. I noticed the keyboard was being presented but it's frame was landscape, when the interface was in portrait. This immediately made me revise the whole way the rotation is being handled.
The whole problem spans from a UIViewController
that posses child view controllers with a complex structure, and the rotation calls are getting forwarded to the children manually. For example, this method:
- (NSUInteger)supportedInterfaceOrientations
{
return [[[(id)self.selectedViewController viewControllers] lastObject] supportedInterfaceOrientations];
}
Was relying on whatever top-most view controller on the current selected navigation controller to return the value. Well, one of the view controllers went rogue, it wasn't returning anything here, so the return value must've been just about anything in memory or zero.
The solution now checks if the view controllers responds to the method, and if not, it defaults to portrait (which is about 90% of the app).
- (NSUInteger)supportedInterfaceOrientations
{
NSUInteger supported = UIInterfaceOrientationMaskPortrait;
UIViewController *viewController = [[(id)self.selectedViewController viewControllers] lastObject];
if (viewController && [viewController respondsToSelector:@selector(supportedInterfaceOrientations)])
supported = [viewController supportedInterfaceOrientations];
return supported;
}
Btw, the sane way to do it would be to just return YES
from -(BOOL)shouldAutomaticallyForwardRotationMethods
(iOS 6, 7) but this was an impossibility due to how it was implemented. For now, it's on NO
and being manually handled.
There's still the question why it only mattered with presented view controllers. Although, I rather just discard it as "random behavior from returning an invalid interface orientation".
UIViewController/UIView orientation frame/bounds in landscape only app
Not sure if this is right, but it's my best guess and I'm unable to test at this moment. If I'm not mistaking, the default orientation is portrait for any application. So, in order to support different orientations, your application should implement the auto rotation methods (are a bit different depending on the iOS version you're building on). So even if you application is ticked to only support landscape mode, it never actually rotates. Try implementing the specified methods and let me know how it goes...
for iOS 5 and earlier you should use:
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
return YES;
}
return NO;
}
for iOS 6 and later you should use:
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
-(BOOL)shouldAutorotate
{
return YES;
}
If you check the view's frame after the rotation takes place, it should be allright.
EDIT:
Take a look at this SO question and it's answers. They provide some nice workarounds. Also, given that in an application you will mostly have your view controllers embedded in a navigation controller or tab bar controller or even both, you can go ahead and subclass/create category on them to make sure that everything is forwarded to your view controllers.
Another great answer explains what's actually happening.
UIViewController viewDidLoad incorrect width/height
If you are targetting iOS 5.0 or better you can use viewWillLayoutSubviews
and viewDidLayoutSubviews
to make changes.
As for your second question, if you need access to an instance variable in other method than init
, you need to keep it around, I don't see a problem with it.
You can, however, try to use Auto Layouts and set up rules between the subviews so it's automatically laid out for you without the need to keep a reference.
How to control a UIViewController's view frame
Your calling of viewController.view
(followed by .frame
) is what triggers the call to viewDidLoad
.
The proper solution is to layout the subviews in the viewWillLayoutSubviews
method of your view controller, not in viewDidLoad
.
Related Topics
Swift - How to Get the File Path Inside a Folder
Issue Detecting Button Cellforrowat
Uinavigationbar Custom Back Button Without Title
How to Detect If App Is Being Built For Device or Simulator in Swift
Enabling Auto Layout in iOS 6 While Remaining Backwards Compatible With iOS 5
How Does View Controller Containment Work in iOS 5
Are There APIs For Custom Vibrations in Ios
How to Pass Data Using Notificationcenter in Swift 3.0 and Nsnotificationcenter in Swift 2.0
Drawing a Route in Mapkit in Ios
Swiftui | Using Ondrag and Ondrop to Reorder Items Within One Single Lazygrid
Xcode 6 - Xcassets For Universal Image Support
How to Add Objects to a Uiscrollview That Extend Beyond Uiview from Storyboard
Mkmapview: Instead of Annotation Pin, a Custom View