I have a UINavigationController
that can rotate to landscape mode and that it initializes a new UIViewController and pushes it on the stack. On the UIViewController's
viewDidLoad
I just print the self.view.frame.size
.
If the UINavigationController
is in Portrait mode, the View Controller will print {320, 460}. However, when the Navigation Controller is in Landscape, the frame size is exactly the same. (To clarify, in the second case the UIViewController
is initialized while the phone is already in landscape mode.)
Shouldn't the frame size of the view be rotated? And if not how I can I find the correct frame size without hardcoding the numbers?
You can't rely on the frame in landscape mode; you have to use a combination of bounds and center; frame is generated using a combination of those, and when there's a non-identity transform applied (as there is in landscape), it gets a little weird.
first you need to set your view to resize automatically with a proper autoresizingMask. with this your view will adapt to the size of the controller itself.
you can check this yourself with an NSLog. But don't put it in loadView, this is too early. Put this in viewWillAppear.
If you set the autoresizingMask of your view with Interface Builder you should turn off the Simulated Interface Elements in the Attributes inspector. If any of these is on you can't change the autoresizingMask in the Size inspector.
This is a pain and it's still true in iOS 4.2 for iPads. The way I solved this is to subclass the UIView
associated with the UIViewController
. I did this in interface builder but I suppose one could also do this in code somehow. In interface builder select the UIView in the UIViewController
then hit the (i) icon in the upper right of the Inspector window. Under class identity hit the popup and chose the UIView subclass below.
The approach is that this UIView subclass overrides the layoutSubviews method, finds the next UIViewController
, determines if it implements a reactToLayout method (which is a method that you have to implement in the UIViewController
subclass for the view controller of this view). If the reactToLayout method exists in the first UIViewController
found, it is invoked.
The reactToLayout method in the view controller then does whatever one needs to do which it will be able to do successfully since the view's frame is set up properly by this time (unlike in ViewDidLoad
, viewWillAppear
, or even viewDidAppear
). I have a method that I call anytime the orientation or frame changes. It's a pain but I store the last frame laid out and last orientation laid out in internal variables of the view controller. The internal layout for new orientation or frame change method compares these to the view's current frame and requested or current orientation so that it doesn't unnecessarily layout stuff over and over.
Here's the code:
UILayoutSubviewsView.h
#import <UIKit/UIKit.h>
@interface UILayoutSubviewsView : UIView {
}
@end
UILayoutSubviewsView.m
#import "UILayoutSubviewsView.h"
// Create this to avoid a warning that this method does not exist for UIViewControllers
// this is OK since we check to see that it does exist before invoking it
@interface UIViewController(UndocumentedMethodForUIViewController)
-(void) reactToLayout;
@end
@implementation UILayoutSubviewsView
// Pass this up to our view controller if it supports the reactToLayout method
// (this is the whole reason for the class)
-(void) layoutSubviews {
[super layoutSubviews];
// Look for the first next responder that is a UIViewController
UIViewController *ourViewController = nil;
id myNextResponder = [self nextResponder];
while (myNextResponder != nil && ourViewController == nil) {
if ([myNextResponder isKindOfClass:[UIViewController class]]) {
ourViewController = myNextResponder;
}
else {
myNextResponder = [myNextResponder nextResponder];
}
}
// If we got a view controller, then see if it supports the reactToLayout method
if (ourViewController != nil) {
if ([ourViewController respondsToSelector:@selector(reactToLayout)]) {
// Invoke the view controller's reactToLayout method
[ourViewController reactToLayout];
}
}
}
@end
YourViewController.h
#import <UIKit/UIKit.h>
@interface YourViewController : UIViewController {
CGRect lastLayedOutFrame;
UIInterfaceOrientation lastLayedOutOrientation;
}
#pragma mark -
#pragma mark Instance Methods
-(id) init;
-(void) reactToLayout;
@end
YourViewController.m
#import "YourViewController.m"
#pragma mark Private Interface Category
@interface YourViewController()
-(void) setViewForCurrentFrameAndRequestedOrientation:(UIInterfaceOrientation) interfaceOrientation;
@end
@implementation YourPadViewController
-(id) init {
// First our super then set ourselves up
if (self = [super initWithNibName:@"YourViewController" bundle:nil]) {
// Initialize some basic stuff
lastLayedOutFrame = CGRectZero;
lastLayedOutOrientation = UIDeviceOrientationUnknown;
}
return self;
}
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
-(void) viewDidAppear:(BOOL) animated {
[super viewDidAppear:animated];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
-(void) reactToLayout {
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
#pragma mark -
#pragma mark Rotation Support
-(BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation {
return YES;
}
// This is called right before the actual rotation
-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation duration:(NSTimeInterval) duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:interfaceOrientation];
}
// Make the necessary adjustments for the different view orientations
-(void) setViewForCurrentFrameAndRequestedOrientation:(UIInterfaceOrientation) interfaceOrientation {
// Set up the requested orientation (need this to handle the Unknown case)
UIInterfaceOrientation requestedOrientation;
if (interfaceOrientation != UIDeviceOrientationUnknown) {
requestedOrientation = interfaceOrientation;
}
else {
requestedOrientation = [[UIDevice currentDevice] orientation];
}
// See if we have anything to do
if (!(CGRectEqualToRect(self.view.frame, lastLayedOutFrame) && lastLayedOutOrientation == requestedOrientation)) {
// Do whatever needs to be done
// Record our last layed out frame and orientation
lastLayedOutFrame = self.view.frame;
lastLayedOutOrientation = requestedOrientation;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With