In the UIViewController rotation method(s) I have a problem when performing the device rotation
inside of the method
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
I am getting incorrect values
According to the documentation:
Sent to the view controller just before the user interface begins
rotating. Subclasses may override this method to perform additional
actions immediately prior to the rotation. For example, you might use
this method to disable view interactions, stop media playback, or
temporarily turn off expensive drawing or live updates. You might also
use it to swap the current view for one that reflects the new
interface orientation. When this method is called, the
interfaceOrientation property still contains the view’s original
orientation. Your implementation of this method must call super at
some point during its execution. This method is called regardless of
whether your code performs one-step or two-step rotations.
I am holding the device strait up (right after launching the app the device is physically in portrait mode) - all the views are correctly aligned for portrait mode
But in the method I am getting wrong values:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// getting the wrong interface orientation here!!!!
// just checking the current orientation for debug
UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
// the first time the device is rotated getting this value for current orientation:
// currentOrientation == UIInterfaceOrientationLandscapeRight
}
even worst than this strange case of wrong orientation is in this same method for 'will rotate to interface orientation' the current interface orientation and the destination interface orientation are identical i.e:
self.interfaceOrientation === toInterfaceOrientation
So whats the point of this method being called by the framework???
This results in all my UIView placement code not being calculating correctly on initial rotation of the device.
Why is this flag not set correctly?!?!
P.S
After rotating the device for the second time - the flags are set correctly and the views "align" themselves properly.
u will get correct value, suppose u are holding the device in port rite , then u turn it t landscape mode, then this method is called
//called first
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//check which orientation hear
if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
NSLog(#"turning t landscape");
}
else
{
NSLog(#"turning t portrite");
}
}
method called hear toInterfaceOrientation contains (for our example) UIInterfaceOrientationLandscapeRightorUIInterfaceOrientationLandscapeLeft`, this indicates that device will going to turn to landscape left or rite
after this, below methods called in order
- (void)viewWillLayoutSubviews
- (void)willAnimateRotationToInterfaceOrientation:duration:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
get called, hear in last method fromInterfaceOrientation contains (for our example) old value or previous orientation flag, that is UIInterfaceOrientationPortraitUpsideDown or UIInterfaceOrientationPortrait
u want t know the current orientation then u can use
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if(UIInterfaceOrientationIsLandscape(orientation))
{
NSLog(#"landscape");
}
else
{
NSLog(#"portrite");
}
for more information about this u can see this docs
Related
I am writing this code to make some changes to how my app is laid out depending on if it is in landscape or portrait mode. I have set up the app to get a notification on the orientation changing like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedRotate:) name:UIDeviceOrientationDidChangeNotification object:NULL];
And then defining receivedRotate:
-(void) receivedRotate: (NSNotification*) notification {
UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];
// ... do something
}
However, UIDeviceOrientationDidChangeNotification is called whenever the device is moved slightly. E.g., the UIDeviceOrientation can be any of the following:
UIDeviceOrientationPortrait,
UIDeviceOrientationPortraitUpsideDown,
UIDeviceOrientationLandscapeLeft,
UIDeviceOrientationLandscapeRight,
UIDeviceOrientationFaceUp,
UIDeviceOrientationFaceDown
This is great but I just want to know if the device orientation is in portrait or landscape, and I also only want to run this code when the orientation actually changes - from portrait to landscape or vice-versa. If they turn the phone all the way around, it is still in landscape mode - just a different form of landscape mode. What's the ideal way to make sure that my rotation code only runs when it is necessary?
DeviceOrientation vs. ScreenSize vs StatusBar.isLandscape?
iOS 11, Swift 4 and Xcode 9.X
Regardless of using AutoLayout or not, there are several ways to get the right device orientation, and they could be used to detect rotation changes while using the app, as well as getting the right orientation at app launch or after resuming from background.
This solutions work fine in iOS 11 and Xcode 9.X
1. UIScreen.main.bounds.size:
If you only want to know if the app is in landscape or portrait mode, the best point to start is in viewDidLoad in the rootViewController at launch time and in viewWillTransition(toSize:) in the rootViewController if you want to detect rotation changes while the app is in background, and should resume the UI in the right orientation.
let size = UIScreen.main.bounds.size
if size.width < size.height {
print("Portrait: \(size.width) X \(size.height)")
} else {
print("Landscape: \(size.width) X \(size.height)")
}
This also happens early during the app/viewController life cycles.
2. NotificationCenter
If you need to get the actual device orientation (including faceDown, faceUp, etc). you want to add an observer as follows (even if you do it in the application:didFinishLaunchingWithOptions method in the AppDelegate, the first notifications will likely be triggered after the viewDidLoad is executed
device = UIDevice.current
device?.beginGeneratingDeviceOrientationNotifications()
notificationCenter = Notifi`enter code here`cationCenter.default
notificationCenter?.addObserver(self, selector: #selector(deviceOrientationChanged),
name: Notification.Name("UIDeviceOrientationDidChangeNotification"),
object: nil)
And add the selector as follows. I split it in 2 parts to be able to run inspectDeviceOrientation() in viewWillTransition(toSize:)
#objc func deviceOrientationChanged() {
print("Orientation changed")
inspectDeviceOrientation()
}
func inspectDeviceOrientation() {
let orientation = UIDevice.current.orientation
switch UIDevice.current.orientation {
case .portrait:
print("portrait")
case .landscapeLeft:
print("landscapeLeft")
case .landscapeRight:
print("landscapeRight")
case .portraitUpsideDown:
print("portraitUpsideDown")
case .faceUp:
print("faceUp")
case .faceDown:
print("faceDown")
default: // .unknown
print("unknown")
}
if orientation.isPortrait { print("isPortrait") }
if orientation.isLandscape { print("isLandscape") }
if orientation.isFlat { print("isFlat") }
}
Note that the UIDeviceOrientationDidChangeNotification may be posted several times during launch, and in some cases it may be .unknown. What I have seen is that the first correct orientation notification is received after the viewDidLoad and viewWillAppear methods, and right before viewDidAppear, or even applicationDidBecomeActive
The orientation object will give you all 7 possible scenarios(from the enum UIDeviceOrientation definition):
public enum UIDeviceOrientation : Int {
case unknown
case portrait // Device oriented vertically, home button on the bottom
case portraitUpsideDown // Device oriented vertically, home button on the top
case landscapeLeft // Device oriented horizontally, home button on the right
case landscapeRight // Device oriented horizontally, home button on the left
case faceUp // Device oriented flat, face up
case faceDown // Device oriented flat, face down
}
Interestingly, the isPortrait read-only Bool variable is defined in an extension to UIDeviceOrientation as follows:
extension UIDeviceOrientation {
public var isLandscape: Bool { get }
public var isPortrait: Bool { get }
public var isFlat: Bool { get }
public var isValidInterfaceOrientation: Bool { get }
}
3. StatusBarOrientation
UIApplication.shared.statusBarOrientation.isLandscape
This also works fine to determine if orientation is portrait or landscape orientation and gives the same results as point 1. You can evaluate it in viewDidLoad (for App launch) and in viewWillTransition(toSize:) if coming from Background. But it won't give you the details of top/bottom, left/right, up/down you get with the notifications (Point 2)
I believe what you're looking for is UIInterfaceOrientation, not UIDeviceOrientation. To use this, just implement the following function in your view controller.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation currentOrientation = self.interfaceOrientation;
//
}
This enum is declared as the following
typedef enum : NSInteger {
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} UIInterfaceOrientation;
Save the previous orientation in a property and compare it with current one.
#0x7ffffff's and #Kunal Balani's answers are down the right path, but I'd recommend you take a look at UIViewController's documentation for rotation handling on iOS 6+ applications vs how it's handled in iOS 5 and older, since you don't mention whether or not you're supporting iOS 5 (which at the time of writing of this answer is still supported). This will help resolve any issues you could run into down the road. I'd recommend specifically taking a look at willRotateToInterfaceOrientation:duration: / willAnimateRotationToInterfaceOrientation:duration: / didRotateFromInterfaceOrientation: and when they are called during the lifecycle of a rotation.
You will definitely want to store the previous orientation as mentioned in the other answers so you don't run your code more than once.
As for your specific question on more "agnostic" as to what version of landscape vs portrait, UIKit's function reference contains a number of pertinent macros, specifically UIInterfaceOrientationIsPortrait() and UIInterfaceOrientationIsLandscape().
I have an app that supports orientation changes and rotates accordingly. I listen to the event UIApplicationWillChangeStatusBarOrientationNotification. Now during the load of this app I add some views and set them up in my app, based on the current orientation.
in iOS 6, this works fine and the app responds and rotates properly, so the user can load in both landscape and portrait mode and code works fine.
in iOS 5, if I load the app in portrait mode, the app works fine, and once that load has been completed in portrait mode, and the UI is aligned and sized, it will respond to other orientation changes to landascape or portrait. The problem I have is this : When loading iOS 5 in landscape mode, and while physically laying the device with iOS 5 on a flat surface to ensure its landscape, I get a OrientationNotification that moves from Landscape to portrait ( although the device didn't change ).
So another device iOS 6 in the same experiment, loads properly and I don't get any weird events of rotation changes that didn't occur, but with iOS 5 I do get them!!
Any ideas?
I am supporting the orientation for both iOS's
- (BOOL)shouldAutorotate {
return YES;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait ||
interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
It sounds like iOS 5 believes things should be portrait if, in fact, there is no physical orientation (i.e. flat up or down) and iOS 6 doesn't. For what it might be worth, to determine the orientation to display stuff when it matters, I use the actual device orientation when available and the status bar orientation when the device is flat. For instance:
// Get a useful screen orientation.
// If the device is physically in portrait or landscape then
// that is the orientation.
// If it is not, then it is probably flat on a table and use
// the orientation of the status bar which should be set to
// the last physical orientation.
//
// screen Orientation
//------------------------------------------------------------
+ (UIDeviceOrientation) screenOrientation {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation!=UIInterfaceOrientationPortrait
&& orientation!=UIInterfaceOrientationPortraitUpsideDown
&& orientation!=UIInterfaceOrientationLandscapeLeft
&& orientation != UIInterfaceOrientationLandscapeRight) {
// Not known at this time. Use the status bar orientation
orientation = [[UIApplication sharedApplication] statusBarOrientation];
}
return orientation;
}
I'm not sure that helps you directly, but perhaps in your notification handler you could check to see what the actual status bar orientation is. Or maybe the timing of the notification and the status-bar orientation change doesn't make that work.
I am building an iPad app in Xcode 4. The app is suposed to always show in Landscape view. to achieve this I have tried the following:
In the Target summary screen I selecte only Landscape Left as a Supported Device Orentation.
In the Target Info screen / Info.plist set the Supported interface orientations(iPad) to Landscape (left home button)
This leads the app the to start in landscape mode, but if I rotate the device it still changes its orientation. Also, when I have a UIViewController presented with presentationStyle UIPresentationFormSheet it rotates to portrait the moment it shows.
In some other threads / forums it was adviced to create a category for UIViewController and rewrite
-(UIDeviceOrientation)interfaceOrientation;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
To always rotate to the Device Orientation (LandscapeLeft) or specifically LandscapeLeft, also to no AutoRotate unless you rotate to LandscapeLeft.
When I set these functions like this (Or for example allow no rotation at all) the app always appears in portrait mode, and wont rotate, not even to LandscapeLeft. The only way to have the app start in Landscape mode is when I allow for rotation no matter what the interfaceOrientaton is.
Does anybody know how I can fix this?
The category I implemented:
#implementation UIViewController(Extends)
-(UIDeviceOrientation)interfaceOrientation
{
return [[UIDevice currentDevice] orientation];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
return YES;
else
return NO;
}
#end
The only place that I can find a Portrait Orientation to be defined is the original window on the MainWindow.xib, but this cannot be altered, and every thread/forum says that that particular setting is/should not be the issue.
As far as I can tell the steps you took should prevent rotation of the interface.
You can always try to override the calls that do the orientation in every viewcontroller of your app. That should at least give you a clue where the rotation is happening. After which a breakpoint can possibly tell you more.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
NSLog( #"will rotate to orientation %d in %#", interfaceOrientation, NSStringFromClass([self class])
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
NSLog( #"did rotate from orientation %d to %d in %#", fromInterfaceOrientation, [self interfaceOrientation], NSStringFromClass([self class])
}
I know how to allow/disallow the orientation to rotate using
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
However, I have the problem now where the phone might be in portrait (or upsidedown) and at some point I want to rotate the screen as if the user rotated to landscape. At this point, I don't want autorotate to work anymore. I want to force the interface orientation to stay in landscape. Is there a way to do this? I can probably figure out a hack for turning off the autorotate, but forcing the rotation in the first place I have no idea how to do.
Here's what I'm trying to do:
The app rotates to any and all orientations. Everything is normal.
An event occurs.
Now the autorotate only works for landscapeleft and landscaperight. Moreover, if the user is in portrait or portraitupsidedown, I programmatically rotate to landscaperight to lock the user into that orientation.
Just to make things trickier, I want to pop up a UIAlertView (I know how to do that) before the forced rotation, letting the user know what's going on, and I want to screen behind the alertView to rotate but not the alertView. Is this even possible?
Thanks.
The best solution for your situation is to notify the user to turn it to landscape orientation and pause the program until the user turns the orientation. And then when the user rotates the device you can handle the delegate methods and start displaying the screen in the landscape mode. This approach has been handled in many of the app store applications and looks like it is one of the best ways to get the app approved in app store. I hope it makes sense.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown || interfaceOrientation == UIInterfaceOrientationLandscapeRight || interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
// orientation view swapping logic
- (void)didRotate:(NSNotification *)notification {
UIDeviceOrientation newOrientation = [[UIDevice currentDevice] orientation];
if (newOrientation != UIDeviceOrientationUnknown || newOrientation != UIDeviceOrientationFaceUp || newOrientation != UIDeviceOrientationFaceDown)
{
orientation = newOrientation;
}
// Write your orientation logic here
if ((orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight))
{
// Clear the current view and insert the orientation specific view.
[self clearCurrentView];
[self.view insertSubview:yourLandscapeView atIndex:0];
} else if (orientation == UIDeviceOrientationPortrait || orientation == UIDeviceOrientationPortraitUpsideDown)
{
// Clear the current view and insert the orientation specific view.
[self clearCurrentView];
[self.view insertSubview:yourPortraitView atIndex:0];
}
}
and add that notification in the event
You cannot. At least not in a way that Apple will allow. The autorotation is designed to be the same for the whole application. You can make code that rotates the view manually, but as far as UIDevice is concerned you'll be in the same orientation.
According to Apple, if you do not want the default behavior provided by the device orientation rotation API:
You can take control over:
The orientations supported by your app
How a rotation between two orientations is animated on screen.
The way I read this is "You can control either of these things, but nothing else" or "You're allowed to control only these two things."
I do not see anything in the documentation that explicitly tells you how to "force" the orientation to change. All of that is handled by the device so as it seem more natural and friendly to the person holding the device. Does that make sense?
See the docs here on rotation changes:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDeviceOrientationChanges/RespondingtoDeviceOrientationChanges.html
Finally, I would say this is not possible, and further that it would not be approved by Apple. I up voted Praveen's answer because I think you may need to consider a different approach for what you're trying to do.
yes you ca do that. what you have to do is in -(void) viewwillappear method simply include the following code
if (self.interfaceOrientation == <current orientation>) {
[[UIDevice currentDevice] performSelector:NSSelectorFromString(#"setOrientation:") withObject:(id)<to which orientation you want to change.>];
}
it will solve your problem
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.