I've never played around with the iPhone being landscape, but I have a fully working iPhone app that is all assuming the user is viewing the iPhone upright. I'd like to play around with rotating the phone, and in order to do that, I did something very simple:
I added the following code to my View Controller:
-(void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
NSLog(#"WILL ROTATE TO INTERFACE ORIENTATION: %#",orientation);
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight))
tableView.frame = CGRectMake(0, 0, 480, 320);
else
tableView.frame = CGRectMake(0,73,320,390);
}
and I'm getting a BAD_ACCESS error. I don't see the view load at all. So, what's the problem, and how do I properly implement the willRotate method so that when the phone rotates, I can resize all my components?
The crash is caused by this line:
NSLog(#"WILL ROTATE TO INTERFACE ORIENTATION: %#",orientation);
orientation is an enum value, therefore the format specifier should be %i not %#.
The simplest way to support rotating is to implement:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
Return YES for the orientations you wish to support. Do not doing any resizing in this method. Use the views struts and springs to ensure that they resize and reposition themselves correctly for the alternate rotations.
It's a problem with your logging code:
NSLog(#"WILL ROTATE TO INTERFACE ORIENTATION: %#",orientation);
The formatting specifier %# in your logging string is a placeholder for an NSString pointer. When the app reaches this point, it looks at the orientation variable, attempts to dereference it to get to an NSString instance, and crashes, because it's not a pointer at all, it's an enum value.
Related
I am using AVFoundation to show the camera.
I would like to prevent the camera itself to rotate so the viewer will see the camera only in portrait and the images will be taken only in portrait mode.
I defined Supported Interface Orientation to support portrait only and the view itself is being displayed only in portrait mode, but not the camera - is being rotated with the device orientation
How can I force the AVFoundation camera to be displayed and capture images only in portrait like the UIViewController?
My code to set the camera:
AVCaptureVideoPreviewLayer* lay = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.sess];
UIView *view = [self videoPreviewView];
CALayer *viewLayer = [view layer];
[viewLayer setMasksToBounds:YES];
CGRect bounds = [view bounds];
[lay setFrame:bounds];
if ([lay respondsToSelector:#selector(connection)])
{
if ([lay.connection isVideoOrientationSupported])
{
[lay.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
}
}
[lay setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[viewLayer insertSublayer:lay below:[[viewLayer sublayers] objectAtIndex:0]];
self.previewLayer = lay;
Here is a partial answer based on my understanding of your question (which differs from the other answers you have had).
You have the app locked to portrait orientation. So the status bar is always at the portrait top of the phone regardless of the phone's orientation. This successfully locks your interface, including your AVCapture interface. But you want to also lock the raw image feed from the camera so that the image horizon is always parallel with the status bar.
This will ideally need to be done continuously - so that if you have the camera at a 45degree angle the image will be counter-rotated 45 degrees. Otherwise, most of the time, the image will not be aligned correctly (the alternative is that it is always out of line until your 90degree orientation switch updates, which would swivel the image 90 degrees).
To do this you need to use Core Motion and the accelerometer. You want to get angle of the phone's Y-axis to true vertical and rotate the image accordingly. See here for geometry details:
iPhone orientation -- how do I figure out which way is up?
Using Core Motion, trigger this method from viewDidLoad
- (void)startAccelerometerUpdates {
self.coreMotionManager = [[CMMotionManager alloc] init];
if ([self.coreMotionManager isAccelerometerAvailable] == YES) {
CGFloat updateInterval = 0.1;
// Assign the update interval to the motion manager
[self.coreMotionManager setAccelerometerUpdateInterval:updateInterval];
[self.coreMotionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error) {
CGFloat angle = -atan2( accelerometerData.acceleration.x,
accelerometerData.acceleration.y)
+ M_PI ;
CATransform3D rotate = CATransform3DMakeRotation(angle, 0, 0, 1);
self.previewLayer.transform = rotate;
}];
}
}
a b c
phone held (a) portrait; (b) rotated ~30deg; (c) landscape
.
You may find this is a little jumpy, and there is a bit of a lag between the device movement and the view. You can play with the updateInterval, and get in deeper with other Core Motion trickery to dampen the movement. (I have not treated the case of the phone being exactly upside down, and if you hold the camera face down or face up, the result is undefined fixed with updated code/ use of atan2).
Now orientation is reasonably correct, but your image does not fit your view. There is not a lot you can do about this as the format of the raw camera feed is fixed by the physical dimensions of it's sensor array. The workaround is to zoom the image so that you have enough excess image data at all angles to enable you to crop the image to fit the portrait format you want.
Either in Interface Builder:
set your previewLayer's view to square centered on it's superview, with width and height equal to the diagonal of the visible image area (sqrt (width2+height2)
Or in code:
- (void)resizeCameraView
{
CGSize size = self. videoPreviewView.bounds.size;
CGFloat diagonal = sqrt(pow(size.width,2)+pow(size.height,2));
diagonal = 2*ceil(diagonal/2); //rounding
self.videoPreviewView.bounds = (CGRect){0,0,diagonal,diagonal};
}
If you do this in code, resizeCameraView should work if you call it from your viewDidLoad. Make sure that self.videoPreviewView is your IBOutlet reference to the correct view.
Now when you take a photo, you will capture the whole of the 'raw' image data from the camera's array, which will be in landscape format. It will be saved with an orientation flag for display rotation. But what you may want is to save the photo as seen onscreen. This means that you will have to rotate and crop the photo to match your onscreen view before saving it, and remove it's orientation metadata. That's for you to work out (the other part of the 'partial answer'): I suspect you might decide that this whole approach doesn't get you what you want (I think what you'd really like is a camera sensor that hardware-rotates against the rotation of the device to keep the horizon stable).
update
changed startAccelerometerUpdates to get angle from atan2 instead of acos, smoother and takes account of all directions without fiddling
update 2
From your comments, it seems your rotated preview layer is getting stuck? I cannot replicate your error, it must be some other place in your code or settings.
So that you can check with clean code, I have added my solution into Apple's AVCam project, so you can check it against that. Here is what to do:
add the Core Motion framework to AVCam.
In AVCamViewController.m
#import <CoreMotion/CoreMotion.h>
add my startAccelerometerUpdates method
add my resizeCameraView method (stick both of these methods near the top of the class file or you may get confused, there are more than one #implementations in that file)
add the line: [self resizeCameraView]; to viewDidLoad (it can be the first line of the method)
add the property
#property (strong, nonatomic) CMMotionManager* coreMotionManager
to the #interface (it doesn't need to be a property, but my method assumes it exists, so if you don't add it you will have to modify my method instead).
In startAccelerometerUpdates change this line:
self.previewLayer.transform = rotate;
to:
self.captureVideoPreviewLayer.transform = rotate;
also, in the Objects list in AVCamViewController.xib, move the videoPreview View above the ToolBar (otherwise when you enlarge it you cover the controls)
Be sure to disable rotations - for iOS<6.0, that is already true, but for 6.0+ you need to select just portrait in supported orientations in the target summary.
I think that is a complete list of changes I made to AVCam, and the rotation/orientation is all working very well. I suggest you try doing the same. If you can get this to work smoothly, you know there is some other glitch in your code somewhere. If you still find your rotations stick, I would be curious to know more about your hardware and software environment such as which devices are you testing on.
I am compiling on XCode 4.6/OSX10.8.2, and testing on:
- iPhone4S / iOS5.1
- iPhone3G / iOS6.1
- iPad mini / iOS6.1
All results are smooth and accurate.
I guess you need to use this method to restrict the camera rotation.
AVCaptureConnection *videoConnection = [CameraVC connectionWithMediaType:AVMediaTypeVideo fromConnections:[imageCaptureOutput connections]];
if ([videoConnection isVideoOrientationSupported])
{
[videoConnection setVideoOrientation:[UIDevice currentDevice].orientation];
}
Assuming your preview layer is defined as property, can use
[self.previewLayer setOrientation:[[UIDevice currentDevice] orientation]];
In your case you can replace [[UIDevice currentDevice] orientation] by UIInterfaceOrientationPortrait
edited
Try to add the preview layer when you actually need it.
Example
preview = [[self videoPreviewWithFrame:CGRectMake(0, 0, 320, 480)] retain];
[self.view addSubview:preview];
The videoPreviewWithFrame function.
- (UIView *) videoPreviewWithFrame:(CGRect) frame
{
AVCaptureVideoPreviewLayer *tempPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:[self captureSession]];
[tempPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
tempPreviewLayer.frame = frame;
UIView* tempView = [[UIView alloc] init];
[tempView.layer addSublayer:tempPreviewLayer];
tempView.frame = frame;
[tempPreviewLayer autorelease];
[tempView autorelease];
return tempView;
}
Assuming your previewlayer is added to a viewcontroller view. Do this in viewDidLoad :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
and define the selector as:
- (void)orientationChanged:(NSNotification*)notification {
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if ([self.previewlayer respondsToSelector:#selector(orientation)]) {
//for iOS5
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.orientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
} else {
//for iOS6
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.connection.videoOrientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
}
}
Note: put tempPreviewLayer in the property self.previewlayer .
This will force the preview layer to portrait position when the device orientation changes.
EDIT
you can also add this in ur 'shouldAutoRotate` method of the viewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if ([self.previewlayer respondsToSelector:#selector(orientation)]) {
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.orientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
} else {
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.connection.videoOrientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
}
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
for ios6 over ride these two and check.
-(NSUInteger)supportedInterfaceOrientations {
//UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
//return (
//interfaceOrientation == UIInterfaceOrientationLandscapeLeft |
//interfaceOrientation == UIInterfaceOrientationLandscapeRight);
return UIInterfaceOrientationMaskLandscape;//(UIInterfaceOrientationLandscapeLeft | UIInterfaceOrientationLandscapeRight);
}
- (BOOL)shouldAutorotate {
return YES;
}
before return in these two methods apend the code ..and in the notification that i gave, see if its called when you roate the device.
Question 1:
How do I get the correct size of a UIView?
I am creating a CGRect to show some images using tiled layers.
When I'm creating the CGRect, I basically need it to be the exact same size as that of my UIView. This turned out to be quite hard..
When I NSLog() out my mainView.bounds.size.width or my mainView.frame.size.width they are always wrong when in landscape! They always log out the values as if it was in portrait, even though I can see the actual view being wider. And reversing them will also be wrong.
It's not good enough set the width to be the height and vice versa when in landscape, I need the right values.
The only way I've been able to make it look right is to manually put in 1024 for width when in Landscape, and this doesn't always work either, because:
Question 2:
What is the correct way to check if the device is in landscape or not?
I've been using
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft || [UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight)
but this doesn't always work. If I hold my device in landscape mode when launching, it is correct, but if I make it landscape, then lay the iPad flat down leaving the dashboard as landscape and THEN launch it, then the landscape-splash shows up, but that code think it's in portrait.
That code doesn't work at all for iPad simulator either..
EDIT
For some reason, when I decided to add support for landscape orientation, it wasn't enough to just check the landscape-orientations in the summary-page of the target, I had to actually sub-class my TabBarController and physically tell it to rotate with
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
I shouldn't have to do this.. right? If I create an empty project like that, it doesn't need it.. I don't know why.
Question 1:
Yup, that's right. :)
Question 2:
I just got home and checked my own code. I use:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
to check the orientation of the current interface. Then, you can use something like:
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
// Do something
} else if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
// Do something else
}
HOWEVER You really should not need to do this if you properly handle rotation events.
Here is my typical way of dealing with rotation when I need to adjust UI element positions in code based on orientation:
#pragma mark - View rotation methods
// Maintain pre-iOS 6 support:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
// Make sure that our subviews get moved on launch:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
[self moveSubviewsToOrientation:orientation duration:0.0];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self moveSubviewsToOrientation:toInterfaceOrientation duration:duration];
}
// Animate the movements
- (void)moveSubviewsToOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
{
[UIView animateWithDuration:duration
animations:^{
[self.tableView reloadData];
if (UIInterfaceOrientationIsPortrait(orientation))
{
[self moveSubviewsToPortrait];
}
else
{
[self moveSubviewsToLandscape];
}
}
completion:NULL];
}
- (void)moveSubviewsToPortrait
{
// Set the frames/etc for portrait presentation
self.logoImageView.frame = CGRectMake(229.0, 21.0, 309.0, 55.0);
}
- (void)moveSubviewsToLandscape
{
// Set the frames/etc for landscape presentation
self.logoImageView.frame = CGRectMake(88.0, 21.0, 309.0, 55.0);
}
I also put moveSubviewsToOrientation in viewWillAppear to have it rotate
I struggled with this a bit and here are some facts I found:
1- When a device is face up or down, the device reverts to the last orientation prior to it being face up or down since those 2 orientations do not tell you necessarily on their own whether the device is portrait or landscape. So for example, if you were in landscape and then put the device flat face up, then launch an app, it will launch in the landscape orientation.
2- When viewDidLoad is called, the bounds have not been set, so you need to put any calls that pertain to the orientation in viewWillAppear or viewDidLayoutSubviews.
3- If for some odd reason, you need to use the bounds before viewDidLoad, or maybe to do something in a model, I have found that the best way to put settings that pertain to the orientation is to trust the statusbar, which you can call as follows for example.
if(UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]))
PS: Regarding your added question, refer to:
https://developer.apple.com/library/ios/#qa/qa2010/qa1688.html
You are most likely one of the last 2 bullets. I ran into this issue before and quite frankly found it easiest to just implement:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
for all VCs just to be on the safe side especially that the behaviour has changed from iOS5 to iOS6.
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDeviceOrientationChanges/RespondingtoDeviceOrientationChanges.html#//apple_ref/doc/uid/TP40007457-CH7-SW1
Hope this helps
I am doing a app for iPad in which we have added two images one for Landscape and the other for portrait mode, I have written code for.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// in this method we have called landscape and portrait frames basing on the
// status bar rotation if it is turned to landscape then the landscape image
// is called and the textfields frames are set else portrait image is set and
// then the textfield frames are set on to it.
}
Can somebody help me out by saying is the right way of using 2 different images, or we should use only 1 image for both landscape and portrait.If so, how can we use those only single image? My app is crashing due to this. Kindly do help me.
Thanks in advance.
There is no harm in using two different images it just increases the size of the image bundle.. i have done this for an app on appstore.. an MNC's banks ipad app. So no harm go ahead and use it. I personally it is better this way than to process the image and resize it.
Hello kinthali As per the documentation
Handling View Rotations
By default, the UIViewController class displays views in portrait mode
only. To support additional orientations, you must override the
shouldAutorotateToInterfaceOrientation: method and return YES for any
orientations your subclass supports. If the autoresizing properties of
your views are configured correctly, that may be all you have to do.
However, the UIViewController class provides additional hooks for you
to implement additional behaviors as needed.
To temporarily turn off features that are not needed or might
otherwise cause problems during the orientation change, you can
override the willRotateToInterfaceOrientation:duration: method and
perform the needed actions there. You can then override the
didRotateFromInterfaceOrientation: method and use it to reenable those
features once the orientation change is complete.
As per your question all you need to do is to return Yes for supported orientation in shouldAutorotateToInterfaceOrientation and you can call to a function to set the background image (Two different images for Portrait and landscape orientation)as follows
[self updateBackgroundImageForOrientation:PROBABLE_INTERFACE_ORIENTATION];
In the method implementation you can update the background image.This way you can implement the desired behavior. Since you have not actually posted any functional code it is hard to point out the cause of the crash. You can debug and see the console logs for root cause of the crash.
You are probably best having two images and you can do the following:
EDIT: I've added some code which will make sure that your image fills the entire space available.
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if((self.interfaceOrientation == UIDeviceOrientationLandscapeLeft) || (self.interfaceOrientation == UIDeviceOrientationLandscapeRight)){
[landscapeimage setHidden:NO];
[portraitimage setHidden:YES];
CGRect landscapeframe = landscapeimage.frame;
landscapeframe.origin.x = 0; // new x coordinate
landscapeframe.origin.y = 0; // new y coordinate
landscapeframe.size.width = 1024;
lanscapeframe.size.height = 768;
}
else if((self.interfaceOrientation == UIDeviceOrientationPortrait) || (self.interfaceOrientation == UIDeviceOrientationPortraitUpsideDown)){
[landscapeimage setHidden:YES];
[portraitimage setHidden:NO];
CGRect portraitframe = portraitimage.frame;
portraitframe.origin.x = 0; // new x coordinate
portraitframe.origin.y = 0; // new y coordinate
portraitframe.size.width = 768;
portraitframe.size.height = 1024;
}
Hope this helps you!
I had started a project using UIImagePickerController to capture still images. I had abandoned using this simple mechanism because of the poor interaction between overlays and just-captured images. The problem was as follows: If you have an overlay active, and snap a photo, the just snapped image rotates around to do an aspect fill in the portrait orientation, when the user is presented the option to use or retake the image. When this occurs, there is no way to dismiss the overlay, or rotate the overlay to correspond to the new still image preview orientation. Not good.
I decided to attempt to re-implement the still image capture behavior of UIImagePickerController using the various mechanisms from AV Foundation. This has been surprisingly easy to set up, but I still have some lingering issues:
1) I want to re-orienting mechanism to be similar to how the UI works in the standard UIImagePickerController. In this controller, as you know, the button bar always stays fixed to the bottom of the portrait orientation, regardless of how the phone is rotated. Within this button bar, the glyphs in the buttons themselves rotate, but everything else remains fixed. The overlay controls, for flash set, camera toggle, and options, does reorient. How can I get this tab bar to remain pinned while rotating these other elements? Is there a way to exclude certain elements from rotation?
2) Upon doing the automatic reorientation, I want the AVVideoCaptureLayer to always remain live and occupy the entire screen. When the device performs the reorientation rotation, this layer gets sized strangely (shrunk down) and the video feed itself is off by 90 degrees. So apparently this layer is being rotated but not to the full screen dimension, and the video feed itself isn't behaving as expected. I want the video to remain fixed and occupying the full screen through the full rotation, with up always being up, and no resizing of the preview during rotation. But I also want to be able to do as in item 1, with certain elements reorienting.
Any sample code illustrating how to achieve this is greatly appreciated.
just found out that using the orientation property on AVCaptureVideoPreviewLayer is deprecated. here is an updated solution (although the previous solution shared by #Jaret Ward was much less work :) :
use the recommend approach of changing it with setVideoOrientation in AVCaptureConnection. I would add a call to this in viewDidAppear and in one of your rotation callbacks.
//setup a connection to the preview layer
AVCaptureConnection *connection;
connection = [self.videoPreviewLayer connection];
[connection setVideoOrientation:[self avOrientationForDeviceOrientation:[[UIDevice currentDevice] orientation]]];
//translate the orientation
- (AVCaptureVideoOrientation)avOrientationForDeviceOrientation:(UIDeviceOrientation)deviceOrientation {
AVCaptureVideoOrientation result = deviceOrientation;
if ( deviceOrientation == UIDeviceOrientationLandscapeLeft )
result = AVCaptureVideoOrientationLandscapeRight;
else if ( deviceOrientation == UIDeviceOrientationLandscapeRight )
result = AVCaptureVideoOrientationLandscapeLeft;
else if( deviceOrientation == UIDeviceOrientationPortrait)
result = AVCaptureVideoOrientationPortrait;
else if( deviceOrientation == UIDeviceOrientationPortraitUpsideDown)
result = AVCaptureVideoOrientationPortraitUpsideDown;
return result;
}
By setting the orientation property on the video preview layer's connection.
This is just a cleaner version of #CocoaEv's answer. It uses the newer dot syntax in addition to making the orientation fix a function instead of a method.
self.videoPreviewLayer.connection.videoOrientation = AVOrientationFromDeviceOrientation([UIDevice currentDevice].orientation);
AVCaptureVideoOrientation AVOrientationFromDeviceOrientation(UIDeviceOrientation deviceOrientation) {
if ( deviceOrientation == UIDeviceOrientationLandscapeLeft )
return AVCaptureVideoOrientationLandscapeRight;
else if ( deviceOrientation == UIDeviceOrientationLandscapeRight )
return AVCaptureVideoOrientationLandscapeLeft;
else if( deviceOrientation == UIDeviceOrientationPortrait)
return AVCaptureVideoOrientationPortrait;
else if( deviceOrientation == UIDeviceOrientationPortraitUpsideDown)
return AVCaptureVideoOrientationPortraitUpsideDown;
else
return deviceOrientation;
}
I encountered a similar issue in my own efforts and finally let go of the idea of trying to affect the video stream. Instead, I focused on the AVCaptureVideoPreviewLayer itself and came up with this solution:
- (void)orientationChangedMethod {
self.previewLayer.orientation = [[UIDevice currentDevice] orientation];
self.previewLayer.frame = self.view.bounds;
}
Where orientationChangedMethod is a fill in to the (very helpful) code at the.ichibod.com and self.previewLayer is a reference to my AVCaptureVideoPreviewLayer instance.
This answer is deprecated since iOS 6!
I'm trying to create an iPhone application that is always in landscape mode, using the Utility application template. Here's what I did:
Create a new iPhone application project, using the Utility Application template
In Interface Builder, rotate all the views 90 degrees.
In Interface Builder, add a label to the middle of the MainView. Stretch it all the way across the view, set the alignment to centered, and set the autosizing springs so that it can stretch horizontally.
In Info.plist, add the key "UIInterfaceOrientation" with value "UIInterfaceOrientationLandscapeRight"
In the controller classes, change the shouldAutorotateToInterfaceOrientation methods to "return (interfaceOrientation == UIInterfaceOrientationLandscapeRight) || (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);"
Run the app.
When I launch my app, it comes up in landscape orientation, but the main view only covers the top half of the display, and it is stretched horizontally. I get the same results in both the simulator and on an actual device. I've seen it with versions 2.2 and 2.2.1 of the SDK.
I have been able to work around the problem by adding the following step to the above:
Add "self.view.autoresizesSubviews = NO;" to RootViewController's viewDidLoad method after "[super viewDidLoad];".
If I do this, then it works as expected. But this feels like a hack. Why should this be necessary?
I don't think it is a transformation issue. All elements are drawn in the proper orientation and with the proper scaling. The problem seems to be that the bounds rectangles of the main view gets funky. It looks like the height of the main view is being cut by a little more than half, and the width is being increased by about 50%.
If I do the exact same set of steps using the View-based Application template instead of Utility, then everything works as expected. So I'm pretty sure the problem is specific to how a Utility application manages its views.
Anybody understand what's going on here?
I was going to say that setting this key does not rotate your interface; you still need to lay out your content in landscape mode and do the appropriate rotation using CFAffineTransform - see "Launching in Landscape Mode" in iPhone OS Programming Guide. Going to find the reference for you, I found this comment: "To launch a view controller–based application in landscape mode in versions of iPhone OS prior to v2.1, you need to apply a 90 degree rotation to the transform of the application’s root view in addition to all the preceding steps. Prior to iPhone OS 2.1, view controllers did not automatically rotate their views based on the value of the UIInterfaceOrientation key. This step is not necessary in iPhone OS 2.1 and later, however."
So if you're running pre-2.1, you need to add this code to your viewDidLoad method in your view controller. (Otherwise, can you post some code?)
-(void)viewDidLoad
// After loading the view, transform the view so that the co-ordinates are right for landscape
// As described in iPhone Application Programming Guide
// Weird, I'm sure this used to be needed, but it doesn't now. The one in CardScrollViewController is needed though.
{
[super viewDidLoad];
CGAffineTransform transform = self.view.transform;
CGPoint center = CGPointMake(kScreenHeight / 2.0, kScreenWidth / 2.0);
// Set the center point of the view to the center point of the window's content area.
self.view.center = center;
// Rotate the view 90 degrees around its new center point.
transform = CGAffineTransformRotate(transform, (M_PI / 2.0));
self.view.transform = transform;
}
Jane describes the setting of UIInterfaceOrientation to UIInterfaceOrientationLandscapeRight (or UIInterfaceOrientationLandscapeLeft), and the rotation settings recommended in the documentation, but I used a slightly different block of code (to the same end) in my root view controller:
- (void)loadView
{
UIView *primaryView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
primaryView.backgroundColor = [UIColor clearColor];
// Start in landscape orientation, and stay that way
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeRight)
{
CGAffineTransform transform = primaryView.transform;
// Use the status bar frame to determine the center point of the window's content area.
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
CGRect bounds = CGRectMake(0, 0, statusBarFrame.size.height, statusBarFrame.origin.x);
CGPoint center = CGPointMake(60.0, bounds.size.height / 2.0);
// Set the center point of the view to the center point of the window's content area.
primaryView.center = center;
// Rotate the view 90 degrees around its new center point.
transform = CGAffineTransformRotate(transform, (M_PI / 2.0));
primaryView.transform = transform;
}
self.view = primaryView;
[primaryView release];
}
In addition to that, I implemented the following delegate method in my root view controller:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ( (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (interfaceOrientation == UIInterfaceOrientationLandscapeRight));
}
Finally, I was encountering weird glitches with the Simulator not auto-rotating properly, so I needed to implement the following delegate method in my UIApplicationDelegate:
- (void)application:(UIApplication *)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration;
{
// This prevents the view from autorotating to portrait in the simulator
if ((newStatusBarOrientation == UIInterfaceOrientationPortrait) || (newStatusBarOrientation == UIInterfaceOrientationPortraitUpsideDown))
[application setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];
}
After all that, my application was able to start in landscape (right) and stay in that orientation under the 2.0 firmware and in the Simulator.
Try setting the orientation property of the view to Landscape in the nib. This property can be found in 4th tab[Attributes Inspector] of Info View of the UIView under Simulated Metrices.