I've got a massive problem and I really hope you can help me here... I'm very lost at the moment :(
I've got a project that runs with a MainWindow.xib. In my app delegate file I check the orientation of the device and load an appropriate NIB file that have different layouts (subviews) based on orientation. Here is the code to check the orientation:
-(void)checkTheOrientation
{
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight)
{
viewController = [[[MyViewController alloc] initWithNibName:#"MyWideViewController" bundle:nil] autorelease];
NSLog(#"Landscape = MyWideViewController");
}
else if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown)
{
viewController = [[[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil] autorelease];
NSLog(#"Portrait = MyViewController");
}
}
This works as expected and in Landscape or Portrait I am loading the correct views. The portrait view loads perfectly but the Landscape view loads with a thick black edge to the left as if it's x & y positions are not set to 0 & 0 respectively.
Here is the portrait view: http://uploads.socialcode.biz/2f25352B0e3x3z2t2z21
Here is the landscape view with the bug: http://uploads.socialcode.biz/1G2k3T012d1z0Y1U2q1k
In the MyViewController.m file I have a rough fix to get the sizing done correctly to avoid this big black strip on the left. Here's the code for this:
- (void) performLayout {
// Ensure the main view is properly placed
NSInteger MaxSizeHeight, MaxSizeWidth;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
MaxSizeHeight = 1024;
MaxSizeWidth = 768;
}
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) {
CGRect mainViewFrame = [[self view] frame];
float width = mainViewFrame.size.width;
mainViewFrame.size.width = mainViewFrame.size.height;
mainViewFrame.size.height = width;
mainViewFrame.origin.x = 0;
mainViewFrame.origin.y = 0;
[[self view] setFrame:mainViewFrame];
} else {
CGRect mainViewFrame = [[self view] frame];
mainViewFrame.origin.x = MaxSizeWidth - mainViewFrame.size.width;
mainViewFrame.origin.y = MaxSizeHeight - mainViewFrame.size.height;
[[self view] setFrame:mainViewFrame];
}
// Ensure the content view is properly placed
CGRect contentFrame = [mainContentView frame];
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight)
{
contentFrame.size.width = MaxSizeHeight;
contentFrame.size.height = MaxSizeWidth;
}
contentFrame.origin.x = 0;
contentFrame.origin.y = 44;
[mainContentView setFrame:contentFrame];
// Ensure the content subviews are properly placed
contentFrame.origin.y = 0;
for (UIView *contentView in [mainContentView subviews]) {
[contentView setFrame:contentFrame];
}
}
The problem with this method is that this is just a very bad hack and it's not actually solving my problem. When it loads up in Landscape it now resizes and positions the subview to 1024x768,0,0 but any additional subviews that I load via other NIBs have the same problem.
What I would really like to know is how on earth can I set the landscape main superview to be 1024x768 position 0 & 0 without having to try and hack this together and keep performing the performLayout selector? At the moment there is a lot of inconsistence with this as the hack doesn't actually set the superview sizing correctly but rather just the subviews I load on top of the superview.
I thought that maybe a simple fix like this might solve the superview issue but alas it doesn't:
window.frame = CGRectMake(0, 0, 1024, 768);
I just want my main superview to be the placed and sized correctly at load and then the superviews just load in the right place. Please can you help me here???
First, UIView has a method named -layoutSubviews that you can override to position the subviews however you like. It'll be called when the view is first loaded, and again before drawing if you ever send it a -setNeedsLayout message.
Second, you should be able to properly set these positions in your .xib or storyboard file. Select your main view in its .xib file and check its position in the Size inspector. Also, ensure that the view is set to Landscape orientation in the Attributes inspector. If it's all correct, then there's something else going on. To simplify the problem, make a new project in Xcode with nothing but an empty view in landscape orientation. I just did one, and there's no position problem. That's what you want in your real app, so figure out what's happening in your real app to affect the view's position that's not happening in your new sample app.
Related
I have two views in my app; "Calculator" and the "Tape". I can click on the button inside calculator to get to the tape and vice versa. I set the rotation as per code below and it works fine most of the time.
However there are issues if I rotate either calculator view or tape view to landscape and then if I try to access other view, interface is all messed up like it doesn't recognize that device was already rotated. Any suggestions?
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self doLayoutForOrientation:toInterfaceOrientation];
}
- (void)doLayoutForOrientation:(UIInterfaceOrientation)orientation {
if (UIInterfaceOrientationIsPortrait(orientation))
{
//set the frames here
}
else
{
//set the frames here
}
}
write these codes in the view will appear of each view.
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight)
{
//adjust the view for landscape
}
else if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
{
//adjust the view for portrait.
}
Here is the problem I've been struggling with:
I'm creating a view programatically using loadView.
Once it's loaded it looks just great in Portrait view. However, I want to handle rotation of the device. Therefore I use willAnimateRotationToInterfaceOrientation method.
Within this method I call a function that adjust all the elements. What that function does is just goes through all my views and sets new CGRect to each of them. It works just fine on portrait orientations (up and upside-down), but once I change orientation to horizontal, it crops.
Two questions:
What is the most likely reason for such behavior?
How would you suggest handling device rotation without creating a separate view for horisontal / vertical orientations?
I think you are missing the key part. When you are setting the frame at portrait view at view did load, view got the frame but when it change to the landscape it change but again from there i think you are not setting the frame for portrait view. use notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
-(void) orientationChanged:(NSNotification *)notification
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIDeviceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if((orientation == UIDeviceOrientationPortrait) || (orientation == UIDeviceOrientationPortraitUpsideDown)) {
// set frame here
}else if ((orientation == UIDeviceOrientationLandscapeLeft) || (orientation == UIDeviceOrientationLandscapeRight)){
// set frame here too
}
}];
}
In many situation need to rotate the controller and is not working.
Right now I have the inverse of the problem: it is rotating, and I want to disable.
In that ViewController I have this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
}
but it is auto-rotating, because not this UIViewController is asked, but his parent in UI tree. Maybe that is the problem. The root controller must return Yes for all cases, because there are a few other UIViewControllers on the stack, which has / must have Portait / Landscape support.
I can't / don't want to touch other parts, because ... there are several reasons, for eg: the app is huge, with lot of know bugs and I don't want to make 1 and test it for 1 week, other is the deadline.
Please don't suggest it shouldn't be like this and must rewritten. I know.
How to deal with this controller to force Portait ?
Please read the bolded text too: can't force the whole app to support only Portait for 1 view controller, there are many on stack!
Try marking the app's supported Interface orientations in the properties file to only being portrait. But then of course in that function you just return YES on view controllers that you want to allow rotation. But then when you push it back in the stack the other views should be portrait.
detect the Landscape rotation and rotate to Portait:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation appOrientation = [UIApplication sharedApplication].statusBarOrientation;
float width = self.view.bounds.size.width;
float height = self.view.bounds.size.height;
//NSLog(#"width %3.0f, height: %3.0f", width, height);
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait || fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
// if is rotated from Portait:
if((appOrientation == UIInterfaceOrientationLandscapeLeft || appOrientation == UIInterfaceOrientationLandscapeRight)){
// to Landscape:
CGAffineTransform transform = self.view.transform;
transform = CGAffineTransformRotate(transform, -(M_PI / 2.0));
self.view.transform = transform;
[self.view setBounds:CGRectMake(0, 0, height, width)];
}
}
else {
// it is rotated from Landscape:
if((appOrientation == UIInterfaceOrientationPortrait || appOrientation == UIInterfaceOrientationPortraitUpsideDown)){
// to Portrait:
CGAffineTransform transform = self.view.transform;
transform = CGAffineTransformRotate(transform, +(M_PI / 2.0));
self.view.transform = transform;
[self.view setBounds:CGRectMake(0, 0, height, width)];
}
}
}
it isn't the best programming paradigm, but it does the trick.
Somebody write similar like tis to accept his answer, or write a better method, if you can!
I am writing an iPad app that needs to know the usable area of the view for drawing purposes. The view is added into a Navigation controller, so I have the status bar plus the navigation controller both taking up a certain number of pixels. My app happens to be in landscape mode, although I don't think that's relevant.
I am able to get the correct view size AFTER rotation using didRotateFromInterfaceOrientation. But I can't figure out how to do it without the screen being rotated.
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
NSLog(#"drfi %d %d", (int)self.view.frame.size.width, (int)self.view.frame.size.height);
}
^^ that works after rotation. Not before. Can't figure out how to get accurate numbers. And I REALLY don't want to hard wire this.
I will also need this function to be device independent -- it should work on the NEW iPad as well as the older iPad resolutions. I can handle the scaling issues once I know the exact usable area. Why is this so hard? Help!!
I don't think you need to specify your frame's view within the didRotateFromInterfaceOrientation what i will suggest instead is setting some properties to your view autoresizing mask so that it automatically resize itself according to your view orientation.
By setting this for example to your view when your view is loaded (viewDidLoad method):
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
you specify that your view will change its width and height automatically and can get the right values you need to get from there.
You should read this: http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1
for a better understanding of views in iOS
EDIT
Also you probably want to spot what is the orientation of your device which can be accomplish with [[UIApplication sharedApplication] statusBarOrientation];
Your application looks like: there is a start up view, then in this view you will load and add a main view into window, right? Then you should do as below in your main view:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
CGRect frame = self.view.frame;
frame.origin.y = frame.origin.y + 20.0;
self.view.frame = frame;
}
return self;
}
Try this.
CGRect frame = [UIScreen mainScreen].bounds;
CGRect navFrame = [[self.navigationController navigationBar] frame];
/* navFrame.origin.y is the status bar's height and navFrame.size.height is navigation bar's height.
So you can get usable view frame like this */
frame.size.height -= navFrame.origin.y + navFrame.size.height;
You can get this dynamically by combining an instance method with a category method:
Instance method:
This assumes that your view controller (self) is embedded within a navigation controller.
-(int)getUsableFrameHeight {
// get the current frame height not including the navigationBar or statusBar
return [MenuViewController screenHeight] - [self.navigationController navigationBar].frame.size.height;
}
Class category method:
+(CGFloat)screenHeight {
CGFloat screenHeight;
// it is important to do this after presentModalViewController:animated:
if ([[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationPortrait ||
[[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationPortraitUpsideDown){
screenHeight = [UIScreen mainScreen].applicationFrame.size.height;
} else {
screenHeight = [UIScreen mainScreen].applicationFrame.size.width;
}
return screenHeight;
}
The above will consistently give you the usable frame height after the status bar and navigation bar have been removed, in both portrait and landscape.
Note: the class method will automatically deduct the 20 pt for the status bar - then we just subtract the navigation header variable height (32 pt for landscape, 44 pt for portrait).
When setStatusBarHidden:NO is set before the view loads, the UINavigationBar and other elements appear aligned immediately below the StatusBar as they should. However, when setStatusBarHidden:NO is set after the view loads, the UINavigationBar is partially covered.
The StatusBar must be revealed after loading the said view, but how can this be done without encountering the aforementioned problem?
I found a hack in a code of mine, though can't remember or find where it came from. The trick is to refresh the navigation bar by hiding and reshowing it:
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController setNavigationBarHidden:NO animated:NO];
In my code the function looks like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}
However, BE WARNED, this is a hack, and currently I'm struggling with some bugs that appear to originate from this code (navigation item doesn't match navigation content). But since it did work for me in some places, I'd thought I'd mention it.
Edit:
I think I found the initial post here:
How do I get the navigation bar in a UINavigationController to update its position when the status bar is hidden?
GL,
Oded
(I realise this was an old question, but I just spent half an hour trying to find the answer myself without success, so I thought I would post it here for anyone else who get stuck... especially if you are trying to SHOW the status bar and your view is ending up overlapping it)
I found this works if you want to HIDE the status bar...
[[UIApplication sharedApplication] setStatusBarHidden:YES];
[self.view setFrame: [[UIScreen mainScreen] bounds]];
but not when you want to SHOW the status bar...
in that case I use this solution which works, but worries me because it hard codes the status bar height to 20...
it also worries me that I have to adjust the view differently depending on orientation. but if I didn't do that it always had the 20 point gap on the wrong edge.
In my case I want to turn the status bar off for some views, and then back on when I return. I had particular problems if I rotated the device while the bar was off. so the switch statement, although ugly (someone might post a cleaner solution), works.
[[UIApplication sharedApplication] setStatusBarHidden:NO];
CGRect frame = [[UIScreen mainScreen] bounds];
switch (self.interfaceOrientation)
{
case UIInterfaceOrientationPortrait:
frame.origin.y = 20;
frame.size.height -= 20;
break;
case UIInterfaceOrientationPortraitUpsideDown:
frame.origin.y = 0;
frame.size.height -= 20;
break;
case UIInterfaceOrientationLandscapeLeft:
frame.origin.x = 20;
frame.size.width -= 20;
break;
case UIInterfaceOrientationLandscapeRight:
frame.origin.x = 0;
frame.size.width -= 20;
break;
}
[self.view setFrame:frame];
My guess is the nav bar is being loaded before the status bar is shown, so the position of the nav bar is (0,0) which then overlaps with the status bar at (0,0). You can just move the frame of the navigation bar (or set up an animation block) in viewDidLoad, after you call setStatusBarHidden:NO.
Try doing navigationBar.frame = CGRectMake(0,20,320,44);
The status bar is 320x20, so just moving your navigation bar down by 20 should accomodate for it.
If you are having this problem because you are not displaying the status bar while your Default.png is loading, and then want to display the status bar immediately upon viewing your first View Controller, just make sure you put [[UIApplication sharedApplication] setStatusBarHidden:NO]; before [self.window makeKeyAndVisible]; in your AppDelegate.m. It happens so quick, you won't ever see the status bar on the splash screen.
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[self.window makeKeyAndVisible];
Here's what I'm doing in my root controller now in iOS 5 after I tell the status bar to animate in. Ugly, but it seems to work.
CGRect rect;
if ( self.interfaceOrientation == UIInterfaceOrientationPortrait )
rect = CGRectMake(0, 20, 320, 460);
else if ( self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown )
rect = CGRectMake(0, 0, 320, 460);
else if ( self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft )
rect = CGRectMake(20, 0, 300, 480);
else
rect = CGRectMake(0, 0, 300, 480);
[UIView animateWithDuration:0.35 animations:^{ self.view.frame = rect; }];
in iOS 7 you can use:
setNeedsStatusBarAppearanceUpdate
for example:
[self.mainViewController.navigationController setNeedsStatusBarAppearanceUpdate];
apple docs:
Call this method if the view controller's status bar attributes, such
as hidden/unhidden status or style, change. If you call this method
within an animation block, the changes are animated along with the
rest of the animation block.
use it only for iOS 7.