Inaccurate device orientation returned by UIDeviceOrientationDidChangeNotification - iphone

I'm listening to the UIDeviceOrientationDidChangeNotification in order to adjust my UI based on the device's orientation.
The problem is that the device orientation I'm getting from the notification seems inaccurate. If I start with the phone in portrait orientation and vertical (as if taking a picture with it) the orientation I get from the notification is correct. As the tilt of the phone approaches horizontal (as in laying flat on a table top) the orientation switches to landscape. This happens much before the phone is actually flat on the table. And this is without rotating the phone towards landscape at all. Is as if it had a preference for landscape.
When using other apps, like mail, I don't see this same behavior. It seems that mail only switches orientation once it's really sure you've gone to the new orientation.
Any help greatly appreciated.
Thanks,

I found my problem.
In viewWillAppear I have:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(didChangeOrientation:) name: UIDeviceOrientationDidChangeNotification object:
[UIDevice currentDevice]];
And here is the notification handler:
- (void) didChangeOrientation: (id)object {
UIInterfaceOrientation interfaceOrientation = [[object object] orientation];
//DLog(#"val is %i", interfaceOrientation);
if (interfaceOrientation == UIDeviceOrientationLandscapeLeft || interfaceOrientation == UIDeviceOrientationLandscapeRight || interfaceOrientation == UIDeviceOrientationPortrait) {
[self orientInterface:interfaceOrientation];
}
By checking that the orientation is one of the two landscapes or the two portraits I'm ignoring the UIDeviceOrientationFaceUp and UIDeviceOrientationFaceDown orientations. That way setting the phone in one of this two orientations won't have an effect on my interface's orientation.
My problem was that I was no considering the faceUp and faceDown and was handling them both in an else statement which was assuming landscape.

If your only interest is your interface orientation (ie landscape or portrait orientation of the device), you should NOT use UIDevice:orientation (or the UIDeviceOrientation* constants if you will), but rather use the UIApplication:statusBarOrientation, which uses the UIInterfaceOrientation* constants.
I use the following code to check for landscape modus:
static inline bool isOrientationIsLandscape() {
return UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]);
}
And for portrait modus:
static inline bool isOrientationIsPortrait() {
return UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]);
}
This cost me a whole morning to figure out, because there is no UIDeviceOrientationFace[Up|Down] on the simulator, only on the real device. So my app worked on the simulator all the time, but on the actual device I had some undefined behavoir during testing every now and then.
Now it works on the actual device like it does on the simulator.
HTH

The notifications are just one way to get at it, you can also read out the accelerometer yourself and implement it in exactly the way you see fit (with a delay and a certain time of non-rotation for example).
Don't know if it's a power drain to get those readouts, but if so, use the notification to know when things are moving, and then fire up the accelometer-reading.

Related

iPhone - too sensitive rotation detection

I have a UIActionSheet based class. The ActionSheets created by this class are designed to vanish if a device orientation rotation is detected.
So, when this class begins I have
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
and on the didRotate: i have the dismiss, that sends the ActionSheet away.
The only little, tiny problem is this: any vibration makes the actionSheet dismiss. Even tapping with a little bit more strength will make the popoup dismiss. Even if you are typing inside the actionSheet.
I don't have any accelerometer or coremotion enabled.
Any clues?
thanks
EDIT
I have discovered that the problem is that initially the orientation is being reported as UIDeviceOrientationUnknown and the trepidation makes it report the correct orientation. As UIDeviceOrientationUnknown is different from the correct orientation, this is seen as a rotation change...
Now I am doomed.
It's difficult to solve this cleanly, because the best methods for detecting the type of rotation you're interested in are called on UIViewController rather than UIView. In UIViewController you have willRotateToInterfaceOrientation:duration:, which tells you that an autorotation is about to occur. This method is on UIViewController because the controller has the responsibility of allowing or refusing autorotation. In order to convey this information to your view, you can post an NSNotification whenever a rotation is about to begin. Then, your UIActionSheet subclass can listen for this notification and shape itself appropriately. Alternatively, you could add a method to your subclass to notify it of rotations directly. It depends on how often you need to listen for autorotation in UIView subclasses and also how strong your need for reuse is.
just check for which interfaceOrientation you're having. since iOS something, PortraitUp and facedown are supported interface orientations. What's happening to you is that portrait (when lying on a table) is switching to face-up and vice-versa. Just check for that. Please be careful, you're looking at UIDevice Orientation, which is NOT UIInterfaceOrientation!
In my case, I was doing
if(orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight)
{
//Do Stuff
}
else
{
//Do Stuff
}
So I was going inside my else on FACEUP and FACEDown event which was wrong !
if(orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight)
{
//Do Stuff
}
else if(orientation == UIDeviceOrientationPortrait)
{
//Do Stuff
}

iPad launch orientation when flat on surface

I have the following code:
-(void)viewWillAppear:(BOOL)animated {
UIDeviceOrientation orientation = [[UIDevice currentDevice] UIInterface];
if(orientation==UIDeviceOrientationLandscapeLeft || orientation==UIDeviceOrientationLandscapeRight) {
NSLog(#"Device Landscape");
} else {
NSLog(#"Device Portrait");
}
}
This works perfectly for the simulator and on the device but only if the iPad is not flat on a surface, for example if i lay the iPad on my desk and load the view it will say its portrait and so I cant detect the orientation when the view appears.
Any ideas of the best way to detect the interface orientation when the viewWillAppear is called?
Thanks
You should use [self interfaceOrientation] instead of the device orientation.
[[UIApplication sharedApplication] statusBarOrientation] also works if you are somewhere in the code that does not have access to [self interfaceOrientation]
First, do you know about the interfaceOrientation property of UIViewController? It looks like you are trying to duplicate it. If you really want to do that, simply subscribe to device orientation change notifications, check if the new orientation is valid interface orientation (the is a macro for that) and if yes, store the value to some variable (like lastSeenOrientation). Then you can rely on that value wherever you want.

Determine UIInterfaceOrientation on iPad

I don't need to specify the orientation in this case, I just need to detect it, but I'm having trouble. I have conditional code that should only work in portrait, and if the device is in landscape I need to do something else. Since the deviceOrientation is not necessarily the same as the interfaceOrientation, I can't come up with a way to test for portrait mode.
Most tutorials I find on Google are ways to force landscape or do some sort of rotation. The only thing I want to do is just determine what the orientation is. Here is my code, which is not working:
-(void)viewDidLoad {
[super viewDidLoad];
//currentOrientation is declared as UIInterfaceOrientation currentOrientation
currentOrientation = [[UIApplication sharedApplication] statusBarOrientation];
NSLog(#"%#",currentOrientation); // == NULL
}
I need to determine the value of the interfaceOrientation and program conditionally. Thanks for your help!
Are you aware of the interfaceOrientation property of the UIViewController class?
- (void) viewDidLoad {
[super viewDidLoad];
BOOL isPortrait = UIDeviceOrientationIsPortrait(self.interfaceOrientation);
// now do whatever you need
}
Or are you after [[UIDevice currentDevice] orientation]?
Especially at launch I have found the following to be always accurate for the UI, regardless of what the UIDevice says the orientation is.
[UIApplication sharedApplication].statusBarOrientation
self.interfaceOrientation is unreliable in certain situations. For example, re-arranging tabs in a tabbar application returns incorrect value.
However [UIApplication sharedApplication].statusBarOrientation is always reliable. You saved me a lot of time slycrel. Thank you.
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if ((orientation == UIInterfaceOrientationLandscapeLeft)
|| (orientation == UIInterfaceOrientationLandscapeRight) )
{
//Landscape
}
else
{
//Portrait
}
I know it is a very old post. How ever I would like to add a point to say it is better to check status bar orientation is better. When ever you call self.interfaceorientation it is calling shouldRotateToOrientation every time. If you have written some code in that method it will be executed. So be cautious!.
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(deviceOrientation == UIDeviceOrientationFaceUp || deviceOrientation == UIDeviceOrientationFaceDown){
if(debug){
NSLog(#"Nothing to change because it is gone to Flat");
}
return;
}
if(deviceOrientation !=statusBarOrientation){
if(debug){
NSLog(#"\nApple has a bug?:\n UIDeviceOrientation : %d, UIInterfaceOrientation: %d",deviceOrientation, statusBarOrientation );
}
}
You won't believe me until you will see at the console the second output!
Some situations - and they exists! - is displayed the last NSLog content!
Than you have to do some workarounds to go on that way, where iOS has no bug, good luck for everyone!
Ah that ... forum moderator maybe will delete this post too, because this doesn't meant to be and answer in his opinion!
I hope it helps for somebody once, it happens on iphone too...(there I got)
Mix it up a little:
BOOL isLandscape = self.view.frame.size.width > self.view.frame.size.height;
(edit) Obviously the previous answers are the correct way to do this and this solution would fail in a situation where view controllers are not full-screen.
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{</br>
if (UIDeviceOrientationIsLandscape(toInterfaceOrientation)) {</br>
some instructions;
} else {
some instructions;
}
}
This is a snippet from one of my programs.
You could of course use the if statement in your ViewDidLoad notification as well.
I already voted up the answer by #slycrel, but I would like to take the time to write this, and point some things out that seems to be lost in this old question, and lots of other questions on the subject.
It's true that Apple does not really want us to update most of our UI based on orientation changes, but it is still totally possible and sometimes necessary on a case by case scenario, and it will be that way until Apple improves their new(ish) APIs (e.g. viewWillTransitionToFrame: would be way more useful than viewWillTransitionToSize:. Just sayin')
Why I voted up the answer by #slycrel is related to what you need to keep in mind as the logical difference between UIDeviceOrientation and UIInterfaceOrientation.
Tthe status bar is what denotes an application's currently known UIInterfaceOrientation. All this stuff about FaceUp, FaceDown is only related to a device's orientation, not necessarily your application's. An application does not support device orientations anyway. Really, UIDeviceOrientation can be ignored completely if all you have to do is make sure you layout and animate things appropriately in your interface, which is 99% of an application developer's use cases. This is currently achieved with the status bar's UIInterfaceOrientation from #slycrel's answer:
[UIApplication sharedApplication].statusBarOrientation
It should be noted, the readwrite version of this property is deprecated, the readonly version is not.
Take this example:
I have an application that supports ALL interfaces orientations, and a root view controller that supports them as well.
Now, I am presenting a UIViewController that will result in the status bar orientation to become landscape.
Which landscape orientation (left or right) it goes to is based on what is returned by preferredInterfaceOrientationForPresentation for that view controller, what the current device orientation is, and what interface orientations the view controller supports (see next point).
The status bar will go to landscape, regardless of what the current device orientation is, because this view controller only supports landscape based on what is returned by supportedInterfaceOrientations. Lets say we support both landscape left and right with UIInterfaceOrientationMaskLandscape.
I also want to conditionally animate this view controller into position with a rotation transform. This will only be necessary when going from portrait or portrait upside down, to landscape left or landscape right. Otherwise it will be a more simple presentation animation without rotation.
Then, after some time and device use, I dismiss that view controller.
Now I want to conditionally animate this view controller off the screen with another rotation transform. This will only be necessary when going from landscape left or landscape right, to portrait or portrait upside down. Otherwise it will be a more simple dismissal animation without rotation.
At this point, the status bar's orientation will become whatever the system decides is appropriate for the combination of your root view controller's preferred interface orientation and supported interface orientations, as well as the device's current UIDeviceOrientation.
Since the view controller we are going to supports ALL interface orientations, if your device's orientation is FaceUp or FaceDown, you can not reliably guess the next UIInterfaceOrientation based on UIDeviceOrientation, and you do not have to anyway.
So... status bar orientation to the rescue!
The previous example is possible, because the status bar orientation is not updated when a view controller transition is about to start (the system asks a transition delegate for an animator, etc.). Then it is updated when the transition starts animating (e.g. by the time animationTransition: is called). This way you should have a good comparison just using the initial and current values of the status bar's UIInterfaceOrientation.
Even without using view controller transitions, it should still be safe to update views based on the status bar orientation.
Keep in mind, if you are manually updating the status bar, and if you are not using "View controller-based status bar appearance" in your Info.plist, then your application's logic must be aware when the status bar will and did change orientation. You will probably be looking for a couple NSNotification names for these cases, which are:
UIApplicationWillChangeStatusBarOrientationNotification
UIApplicationDidChangeStatusBarOrientationNotification
As well as these UIApplicationDelegate methods:
- (void)application:(UIApplication *)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration;
- (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation;
- (UIInterfaceOrientationMask)supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window
And this other helpful UIApplication property:
#property(nonatomic,readonly) NSTimeInterval statusBarOrientationAnimationDuration;
As of iOS8, APIs have been deprecated or return unhelpful results such as .FaceUp .FaceDown
This is because Apple does NOT want you to update your UI using orientation, but rather by using size classes, constraints, and proportion (using n% of superview).
Indeed, orientation dependent code might fail to provide good results across the whole range of device and use case (especially multitasking)

iPhone SDK: Forcing landscape mode for a single part of an app

Yet another landscape mode question.
We've all seen the
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];
Nonsense. I have not been able to get this to work on the simulator or on the device.
What I get is this:
http://i47.tinypic.com/zl9egh.png
when I really want this:
http://i45.tinypic.com/xms6cm.png
As you might be able to tell, it's flipping the phone but it's not realizing it has been flipped.
I've seen the hidden method
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
But I need this app to be on the app store, and I don't know if they will crack down on that.
Does anyone have any idea on how to get this damn thing to straighten itself out?
Ideally your view controller should implement:
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
Then the only allowed orientation for that view controller is one of the landscape orientations.
Unless you have a pretty complex view hierarchy that utilizes a tab bar controller, this should cause the view to rotate when it's pushed.
If it doesn't work you can try:
[[UIDevice currentDevice] performSelector:
#selector(setOrientation:)
withObject:(id)UIInterfaceOrientationLandscapeRight];
I have an app in the AppStore that uses this code because in my case I do have a complex hierarchy of views that keeps the one I need to be landscape only from working.
If you want to rotate a single part of the app you can use the CGAffineTransform transform property of a UIView. For an example check this thread:
How do I apply a perspective transform to a UIView?

How to control the orientation of the device in iphone / ipod?

i have to do something on rotation the device on the portrait mode but when i use the
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
//[[[UIApplication sharedApplication]keyWindow]subviews removeFromSuperview ];
// return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
return YES;
}
following code and it work fine on the simulator but when i use to install the code in the device it make the device so much sensitive that if i just a little change the position in the device it execute this code and go to the view that i m showing on the rotation of the phone
than pls anyone tell me that how can i control the sensitivity of the device i mean that i just want when user complete a 90' rotation in the position than my code should execute but it just execute if just shake the phone in the same position .
thanks for any help
Balraj verma
You probably want to buffer the orientation information; that is, only change the orientation after you have received several indications from the sensor in a row that the orientation is different from what you're currently displaying.
From my understanding of what you have written, your application is too sensitive when the device is rotated? It's difficult to understand why this is; the code you've included states that you're application is willing to accept any rotation, but you've not stated how you deal with the rotation events afterwards.
Based on the info, I can only suggest that you add a check to in your code (perhaps where the rotation event is dealt with) and obtain the orientation from the device using:
[[UIDevice currentDevice] orientation];
which will return a UIInterfaceOrientation enum stating the current orientation of the device.
UIDeviceOrientationPortrait
UIDeviceOrientationPortraitUpsideDown
UIDeviceOrientationLandscapeLeft
UIDeviceOrientationLandscapeRight
You can then use this to establish whether there's a need to change the orientation. (Note: this is iPhone OS 2.0. I believe that OS 3.0 has more including lying face up/down, etc.)
- (void) didRotate:(NSNotification *)notification
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeRight)
{
NSLog(#"Landscape Right!");
}
}
If you find that the orientation switches too quickly you may want to buffer the information, creating several sample points that you can compare before switching to the new orientation.