iPhone accelerometer and device orientation

So Apple released a new beta of iPhone OS 3.0 today. It’s the fifth beta and we’re getting closer to final release of 3.0. There were very few changes or additions to the API so it’s not likely we’ll see more changes at all. In February I published a quite lengthy solution  on how to rotate images without rotating the entire application where I use shouldAutorotateToInterfaceOrientation.  Recently I wrote a post about the changes made to shouldAutorotateToInterfaceOrientation which in my opinion breaks important functionality. As promised here’s how you can replace shouldAutorotateToInterfaceOrientation using the accelerometer.

*UPDATE*
Kyle was friendly enough to post a simple solution to how you can listed to the shouldAutorotateToInterfaceOrientation event. I’ve updated my previous post with his solution (or see his comment below): breaking changes to shouldAutorotateToInterfaceOrientation.

The application is called DeviceOrientation and is just a proof-of-concept, not a full replacement for shouldAutorotateToInterfaceOrientation. Unfortunately the accelerometer cannot be simulated in the simulator, you will have to compile and run it on your iPhone to test. Here’s a dirty clip I recorded with the iSight camera in my iMac, it’s harder than you think to record a clip sitting behind the device and the screen itself adds a nasty glare in the iPhone. At least it’s pretty short, enjoy:


Warning math and code head!

To calculate the angle from the accelerator you use atan2. Instead of trying to explain atan2 myself, here’s wikipedias short explenation:

In trigonometry, the two-argument function atan2 is a variation of the arctangent function. For any real arguments x and y not both equal to zero, atan2(yx) is the angle in radians between the positive x-axis of a plane and the point given by the coordinates (xy) on it. The angle is positive for counter-clockwise angles (upper half-plane, y > 0), and negative for clockwise angles (lower half-plane, y < 0).

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
	// Get the current device angle
	float xx = -[acceleration x];
	float yy = [acceleration y];
	float angle = atan2(yy, xx);
}

I put some effort into creating a, hopefully, easy to understand visualization of how angles relate to the devices actual orientation. It’s easier if you hold the phone in front of you when you look at it and rotate it and compare it to the image:

angles-and-device-orientation
So many iPhones on a single picture, awesome!

Knowing the angles of each orientation is obvious that between each orientation there difference is 1.5 and halfway between each orientation the offset is then 0.75. The following code changes the label text to the interface orientation but only when the orientation changes from one to another:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
	// Get the current device angle
	float xx = -[acceleration x];
	float yy = [acceleration y];
	float angle = atan2(yy, xx); 
 
	// Add 1.5 to the angle to keep the label constantly horizontal to the viewer.
	[interfaceOrientationLabel setTransform:CGAffineTransformMakeRotation(angle+1.5)]; 
 
	// Read my blog for more details on the angles. It should be obvious that you
	// could fire a custom shouldAutorotateToInterfaceOrientation-event here.
	if(angle >= -2.25 && angle <= -0.75)
	{
		if(deviceOrientation != UIInterfaceOrientationPortrait)
		{
			deviceOrientation = UIInterfaceOrientationPortrait;
			[interfaceOrientationLabel setText:@"UIInterfaceOrientationPortrait"];
		}
	}
	else if(angle >= -0.75 && angle <= 0.75)
	{
		if(deviceOrientation != UIInterfaceOrientationLandscapeRight)
		{
			deviceOrientation = UIInterfaceOrientationLandscapeRight;
			[interfaceOrientationLabel setText:@"UIInterfaceOrientationLandscapeRight"];
		}
	}
	else if(angle >= 0.75 && angle <= 2.25)
	{
		if(deviceOrientation != UIInterfaceOrientationPortraitUpsideDown)
		{
			deviceOrientation = UIInterfaceOrientationPortraitUpsideDown;
			[interfaceOrientationLabel setText:@"UIInterfaceOrientationPortraitUpsideDown"];
		}
	}
	else if(angle <= -2.25 || angle >= 2.25)
	{
		if(deviceOrientation != UIInterfaceOrientationLandscapeLeft)
		{
			deviceOrientation = UIInterfaceOrientationLandscapeLeft;
			[interfaceOrientationLabel setText:@"UIInterfaceOrientationLandscapeLeft"];
		}
	}
}

If you want a replacement to shouldAutorotateToInterfaceOrientation you can use this code and instead of changing the text of a label, you fire an event.

Download the code

As I wrote above (in case you skipped directly to the code), this application must be run on the device, the simulator unfortunately doesn’t simulate the accelerometer. The application will run, but it won’t do you much. As always the code is free to use any way you like. All code on this blog is free to use, even if the comments in some files say different.

Download DeviceOrientation demo application (full source included)

19 thoughts on “iPhone accelerometer and device orientation

  1. Correct me if I’m wrong, but I would think that the first “if” statement should have a value of -0.75 rather than -0.25.

    ie: if(angle >= -2.25 && angle <= -0.75)

  2. You should be able to listen for the system “UIDeviceOrientationDidChangeNotification” event and use UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation]; to get the orientation changes in the notification function

  3. Not sure if I am allow to post code but here’s some code for those who are curious:

    -(void) viewWillAppear: (BOOL) animated{

    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

    [[NSNotificationCenter defaultCenter] addObserver: self
    selector: @selector(receivedRotate:)
    name: UIDeviceOrientationDidChangeNotification
    object: nil];

    }

    -(void) receivedRotate: (NSNotification*) notification
    {
    UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];
    if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
    {

    }
    }

    -(void) viewWillDisappear: (BOOL) animated{
    [[NSNotificationCenter defaultCenter] removeObserver: self];
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];

    }

  4. Hey Kyle,

    Awesome code! That does give access to the shouldAutorotateToInterfaceOrientation event! Thanks a lot. I’ll update my other post later with your solution.

  5. Also, the file which defines atan2() also has a bunch of constants defined like M_PI M_PI_2 (pi/2) and M_PI_4 (pi/4) I think your code could be a tiny bit more accurate if you used these instead.

  6. This is a great blog! A question… these solutions appear to be for yaw only. Is it possible to derive pitch and roll too? That would fully describe the device’s orientation.

  7. I get this build error when tring to run the project.
    error: There is no SDK with the name or path ‘iphoneos2.0′

    Can you please help me .

    Thank you
    Shawn

  8. Hei Björn, Takk skal du ha! (15 months in Norway, but this is pretty much all I can say in Norwegian, sorry ;-)

    Thank you very much indeed for your excellent sample code and all the great examples which are extremely useful for iPhone development beginners like myself.

    As I can see from your sample code, the orientation determination always starts when the first view is already added to the window as a subview. Is there a way of starting earlier? Actually I do not like Apple’s approach to start all applications in Portrait Orientation first and only then switch to Landscape Orientation if necessary.

    If I hold my iPhone already in Landscape Orientation and start an app or safari (yes, I know, I shouldn’t), it will show Portrait Orientation first and switch directly to Landscape Orientation afterwards. I would like to prevent this animated orientation switch entirely and start right away with the “correct” orientation of the app accordingly to the device’s physical orientation (yes, I saw your discussion on flipping the whole app’s or just the view orientation of the image view).

    This is a minor issue with the iPhone since the UI of the springboard is not (yet) available in landscape orientation anyway, but rather soon it will be more important, because – even Apple admits – one cannot predict the “user preferred orientation” anymore when it comes to the iPad.

    So, can I already start with this accelerometer determination at the point of “applicationDidFinishLaunching”? I would like to determine the necessary view orientation first (accordingly to the physical orientation of the device) and only then add the respective (portrait or landscape) view controller and view as a subview to the window.

    How would I add a view controller dynamically then (sorry, I am still a beginner)?

    Since I do not love too much this animated rotation of the views, I would rather like to use something which is shown in Apple’s AlternateViews example and to have this quick and neat cross-dissolve effect instead.

    Thanks again for your helpful blog and those great insights.

    Kind regards from Berlin, Lars

  9. Aha, now after having a closer look at your “ScrollViewImageGallery” example, I see how to add a view controller dynamically! Thank you very much for all the comments, very useful.

    Kind regards, Lars

  10. hi there, i am very much inspired by your “ScrollViewImageGallery” example. I would want to add 2 more functionalities – a tile based home page, where i have 16 buttons to press which will lead different groups. I want to create a scrap with lots of pictures.

    Second is to add Tab bar to this gallery. Can you guide me how to do this? Thanks a ton.

  11. You should definitely be adding M_PI_2 instead of 1.5 to the affine transformation angle, or the text isn’t level!

  12. I came up with a page turn based on the change in y for a newsstand app and just wanted to share the code

    if(angle >= -1.75 && angle = 0.30) {

    //NSLog(@”Previous Calibrated Y %.02f “,self.accelerationY);
    //NSLog(@”Current Calibrated Y %.02f “,self.calibratedFilter.y);

    if (self.accelerationY < 0.30) {

    DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
    NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];

    NSLog(@"Current Page Index %d",retreivedIndex);

    [self goToPageNumber:retreivedIndex+2];

    }
    }

    // Backward turn
    if (self.calibratedFilter.y -0.30) {

    DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
    NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];

    NSLog(@”Current Page Index %d”,retreivedIndex);

    [self goToPageNumber:retreivedIndex];
    }
    }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>