I call:
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger: UIInterfaceOrientationLandscapeLeft]
forKey:#"orientation"];
And then I log out:
NSLog(#" = %#",NSStringFromCGRect(self.view.bounds));
And I get:
2015-04-06 11:04:13.02 [1032:262526] = {{0, 0}, {375, 667}}
This is on an iPhone 6. How come the bounds didn't change?
EDIT
So I saw in another answer that [UIScreen mainScreen].bounds gives the proper dimensions after setting rotation programmatically using setValue:forKey.
However, how come the view still has the portrait frame? Is this a delayed response? Do I have to rotate the view myself?
If you want to change interface orientation then you should look towards this callbacks
interfaceOrientation property
– shouldAutorotateToInterfaceOrientation:
+ attemptRotationToDeviceOrientation
– rotatingHeaderView
– rotatingFooterView
– willRotateToInterfaceOrientation:duration:
– willAnimateRotationToInterfaceOrientation:duration:
– didRotateFromInterfaceOrientation:
and I will suggest you to look towards this method too.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
Please refer this docs
adject your code inside this
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
if UIInterfaceOrientationLandscapeLeft{//code} else if UIInterfaceOrientationLandscapeRight{//code}
Related
Our app consists of Cocos3D and AR. Since upgrading to iOS 8 we had to fix our orientation handling because of the changes Apple brought on us.
However, we still experience some issues that lead me to a rather interesting phenomena (I checked it in our app and in a fresh new project..):
Logging the [UIDevice currentDevice].orientation,[UIApplication sharedApplication].statusBarOrientation and [UIScreen mainScreen].bounds.size shows that when the device is rotated quickly, the device orientation actually changes, however the interface orientation sometimes stays the same and it leads to wrong calculations of the screen bounds... e.g.:
before rotation:
[UIDevice currentDevice].orientation = 1
[UIApplication sharedApplication].statusBarOrientation = 1
[UIScreen mainScreen].bounds.size = 414x736
after first rotation:
[UIDevice currentDevice].orientation = 4
[UIApplication sharedApplication].statusBarOrientation = 4
[UIScreen mainScreen].bounds.size = 736x414
after second rotation:
[UIDevice currentDevice].orientation = 1
[UIApplication sharedApplication].statusBarOrientation = 4
[UIScreen mainScreen].bounds.size = 736x414
Now obviously, after the second rotation, the device is back in portrait mode - well not according to the interface orientation and the screen bounds...
Am I missing something here?
I have had this issue in the past and have concluded that when you set up an observer to the UIDeviceOrientationDidChangeNotification the method you specify is called before all attributes change to reflect the new orientation. The way I dealt with it is to create a function which runs a block of code after a delay and then call:
- (void)deviceOrientationDidChange:(NSNotification *)note
{
RHRunBlockAfterDelay(0.5, ^{
// Execute relevant code...
});
}
The delay function is a wrapper around a call to [self performSelector:#selector(mySel) afterDelay:delay withObject:block]; defined in a separate Functions.h.
I know that this looks like a huge question but it's not so don' be afraid to read it. Mostly it's me explaining how stuff works.
I have two UIWindows in my app. The first one is the main window which gets created by the app by default. The second one is called modalWindow and is also created in the app delegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.modalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.modalWindow.windowLevel = UIWindowLevelStatusBar;
//Other stuff I need
//....
return YES;
}
Most of my app is portrait only but there is one view controller where the user can switch to landscape orientation. I'm listening for landscape change via:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
This works fine and in the orientationChanged method I present the landscape view controller. All is well at this point. If I rotate back to portrait I get the orientationChanged fired once more at which point I dismiss the landscape view controller.
Now let's explain how the second window comes into play. When in landscape mode there is an action (just a button press) which presents a new view controller on the second window (the modalWindow). Ok let's explain this.
The app delegate has two methods which look like so:
- (void)presentActivityOnModalWindow:(UIViewController *)viewController
{
self.modalWindow.rootViewController = viewController;
self.modalWindow.hidden = NO;
}
- (void)modalTransitionFinished:(NSNotification *)notif
{
self.modalWindow.hidden = YES;
self.modalWindow.rootViewController = nil;
}
I call the presentActivityOnModalWindow via the [[[UIApplication sharedApplication] delegate] performSelector....]. I know it isn't the best practice but it works fine. As for the dismissal I use NSNotificationCenter to post the notification about dismissal. The view controller that gets presented on the modalWindow is supporting only portrait mode. It's returning YES in shouldAutorotate and UIInterfaceOrientationMaskPortrait in supportedInterfaceOrientations.
Ok a lot of explaining but now we get to the problem. When I rotate to landscape, pop up the modalWindow, dismiss the modal window and rotate back to portrait my main window is still in landscape mode and everything looks catastrophic.
I tried adding an observer to both window and modalWindow and logging changes in frame and bounds and here are the results:
Did finish launching:
window frame {{0, 0}, {320, 568}}
window bounds {{0, 0}, {320, 568}}
modalWindow frame {{0, 0}, {320, 568}}
modalWindow bounds {{0, 0}, {320, 568}}
Rotate to landscape:
window frame {{0, 0}, {568, 320}}
Open activity in landscape:
modalWindow frame {{0, 0}, {320, 568}}
modalWindow frame {{0, 0}, {320, 568}}
Dismiss activity:
none
Rotate back to portrait:
none
So as it seems my window does not get back to normal frame when we get back to portrait mode. How to fix this? If I need to provide any more details feel free to ask.
The system will only handle rotation of your keyWindow. If you have other windows you'll have to handle rotation yourself.
I also think that modal controllers is the way to go. But if you really want to handle rotations take a look at how other "custom windows" libraries handle rotation. Alert views are a great example:
https://github.com/search?o=desc&q=uialertview&s=stars&type=Repositories&utf8=✓
Apple does not encourage using windows in this manner. It is probably best to use an UIViewController displayed modally in stead of your
self.modalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
Presenting an UIViewController modally is done from an UIViewController instance using
[self presentViewController:<#(UIViewController *)#> animated:<#(BOOL)#> completion:<#^(void)completion#>]
You are creating 2 windows and Apple recommends against this. An app should really have as many windows as there are display devices.(SEE UIWindow reference )
I would suggest using 2 views and "drawing" everything onto those views according to the orientation. Then make one view hidden, when the other is displayed.
Alternatively you could just use 1 view and make adjustments at the time of orientation changes. The ease of doing this depends on the type of information being displayed.
Orientation changes are best detected by AppDelegate code.
Here is a sample of code I use to detect an orientation change and then a check for the new dimensions of the window.
- (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation
{
//other stuff from rotate:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect appFrame = [[UIScreen mainScreen ]applicationFrame];//using frame so status bar is not part of calculations.
if (orientation == UIInterfaceOrientationLandscapeRight
||
orientation == UIInterfaceOrientationLandscapeLeft
)
{
self.currentAppScreenWidth = appFrame.size.height;
self.currentAppScreenHeight = appFrame.size.width;
[YOURapp reArrangeSubViews_and_Layouts: appFrame];//Something like this - maybe.
}
else
{
self.currentAppScreenWidth = appFrame.size.width;
self.currentAppScreenHeight = appFrame.size.height;
[YOURapp reArrangeSubViews_and_Layouts: appFrame];//Something like this - maybe.
}
}
Hope that helps.
One other thing. If you are displaying the same info with a different layout, then you should be able to lay it out using Xcode tools, to adapt to orientation in a suitable way. If certain things are still not perfect, then try to tweak using code.
If you are displaying completely different information, the 2 view approach might be the easiest.
[Maybe some more information about the differences in the data being displayed on the 2 screens, if the above solution does not work for you.
Cheers.]
I created a UIViewController (based on How to switch views when rotating) to switch between 2 views when the device rotates. Each view is "specialized" for a particular orientation.
It uses the UIDeviceOrientationDidChangeNotification notification to switch views:
-(void) deviceDidRotate: (NSNotification *) aNotification{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
NSLog(#"Device rotated to %d!", orientation);
if ((orientation == UIDeviceOrientationPortrait) ||
(orientation == UIDeviceOrientationPortraitUpsideDown)) {
[self displayView:self.portraitViewController.view];
}else if ((orientation == UIDeviceOrientationLandscapeLeft) ||
(orientation == UIDeviceOrientationLandscapeRight)) {
[self displayView:self.landscapeViewController.view];
}
}
and sort of works. The problems shows up when I rotate to Landscape and then back to Portrait. When going back to portrait the subviews aren't displayed in the right place, specially the UIPickerView:
First Time Portrait:
Rotate to Landscape:
Back to Portrait:
If I repeat the rotation process, things just get worse. What am I doing wrong?
The source code is here: http://dl.dropbox.com/u/3978473/forums/Rotator.zip
Thanks in advance!
To solve your offset problems, rewrite the displayView: method as below.
-(void) displayView: (UIView *)aView{
self.view = aView;
}
Rotations however are strange. you should review that part of code.
Use the UIViewController rotation methods
(void)willRotateToInterfaceOrientation:duration:
(void)didRotateFromInterfaceOrientation:
instead of -(void)deviceDidRotate:
Much simpler, you will avoid that strange bouncing, and you don't need notifications any more.
Do some reading on the apple documentation on the methods i specified above.
Hope this helps.
OK, I found the error. It's pretty simple and stupid: I mixed frame and bounds.
In the displayView: code I was setting the frame of the child view to the frame of the parent view, when it should be the bounds of the parent.
I use this code:
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
to get iPad orientation but it returns UIDeviceOrientationUnknown when I use OpenGL rendering. When I play movies or I display UIViews it return the correct value. Do you know how may I get correct orientation? I don't want to use the accelerometer because I want to know when the device has locked orientation.
Thank you!
There are situations where the accelerometer can’t get an accurate read on the orientation, such as when the device is held flat. You should still be able to get the current orientation using:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
In iOS 13, the statusBarOrientation has been deprecated. The new preferred way of getting this orientation is
let orientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
If I add [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; the orientation will be correct even in OpenGL rendering mode. UIDeviceOrientationUnknown orientation is sometimes returned but only for short time.
In "View Did Load" I'm trying to determine the size of the view so I can appropriately size a subview. I want it to always stretch about the length and width of the screen regardless of orientation.
quest *anview = [[quest alloc] initWithFrame: CGRectMake(50, 50, self.view.frame.size.width-100, self.view.frame.size.height-100)];
self.aquest=anview;
But this always returns a width of 748 and a height of 1024 when it should return the opposite when I'm in landscape. I know that you can't get orientation on the simulator but this occurs on the device as well. When I get the orientation:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
NSLog(#"device orientation:%d",orientation);
I correctly get the orientation but this log statement returns the 748/1024 regardless of orientation.
NSLog(#"size w:%f",self.view.frame.size.width);
NSLog(#"size h:%f",self.view.frame.size.height);
Anyone know what's going on? Does this have to do with me putting this in the viewdidLoad method? Should I put it in viewWillAppear or viewDidAppear?
Try to use the bounds property instead of frame.
The view is always created in portrait mode and then rotated, even if you launch the application in landscape. You should be able to keep the subview stretched by setting its size relative to the parent's portrait size and the autorsizing mask to UIViewAutoresizingMaskFlexibleWidth|UIViewAutoresizingFlexibleHeight, providing the shouldAutorotateToInterfaceOrientation: return YES for the right device orientation. It will also keep the distance from the top and left margin unless you add to the mask a different UIViewAutoresizingFlexibleMargin.