my App has several uiViews and set to support both orientations. since my uiViews frames are only part of the whole size of the iPad, so I'm trying to center my uiviews frame depending on how the iPad is held aka orientation. It's is done in this manner in the view controller.m file:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
UIInterfaceOrientation des=self.interfaceOrientation;
if(UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad) //iPad
{
CGRect ScreenBounds = [[UIScreen mainScreen] bounds];
if(des==UIInterfaceOrientationPortrait||des==UIInterfaceOrientationPortraitUpsideDown)//ipad-portrait
{
ipStPosX=(ScreenBounds.size.width -360)/2;
ipStPosY=100;
return YES;
}
else//ipad -landscape
{
ipStPosX=(ScreenBounds.size.height -360)/2;
ipStPosY=100;
return YES;
}
}
else//iphone
{
UIInterfaceOrientation des=self.interfaceOrientation;
if(des==UIInterfaceOrientationPortrait||des==UIInterfaceOrientationPortraitUpsideDown) //iphone portrait
{
return NO;
}
else //iphone -landscape
{
return YES;
}
}
}
after launching the app and changing the orientation it will always go to the portrait part, UIInterfaceOrientationPortrait, regardless of how i hold the device.
I saw some old posts that didn't really fit the issue and were confusing or dated so any help here will be great.
shouldAutorotateToInterfaceOrientation: is for simply telling iOS that you support particular orientations. since you want everything but iPhone landscape, you should return
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) //iPad
return YES;
return UIInterfaceOrientationIsPortrait(orientation);
}
for actually adjusting what you display, you want to use willRotateToInterfaceOrientation:duration: or didRotateFromInterfaceOrientation: for actually changing items. in your case, given that you are just adjusting some iVars depending upon which orientation you are in, i presume you will use the latter.
- (void)didRotateToInterfaceOrientation:(UIInterfaceOrientation)fromOrientation
UIInterfaceOrientation des = self.interfaceOrientation;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) //iPad
{
CGRect ScreenBounds = [[UIScreen mainScreen] bounds];
if (UIInterfaceOrientationIsPortrait(des))//ipad-portrait
{
ipStPosX=(ScreenBounds.size.width -360)/2;
ipStPosY=100;
}
else //ipad -landscape
{
ipStPosX=(ScreenBounds.size.height -360)/2;
ipStPosY=100;
}
}
}
I think there are two problems here:
Enabling rotation - This can be done by implementing the method below. You should put a breakpoint in this method to ensure its called and you return YES. Please note that this method is only called for those views that are pushed to the navigation bar or tab bar or are part of the window. This will not be called for views that have been added to other views using addSubView method.
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation;
Implementing the above method and not implementing any other method will rotate your view. If it does not, you need to investigate the points I have outlined above. Once your view starts rotating, you need to then play with the autoresizing masks to achieve what you are planning to do. Centering the views should be easily achievable using autoresizing masks. If using XIBs, you can review the resizing behavior from within xcode.
Related
I'm trying to write an app without using nib, everything I'll do it programmatically.
Now the problem is, how am I going to support both iPad and iPhone? Obviously, I can't do it with
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
// load iPad nib
} else {
// load iPhone nib
}
If I create 2 ViewControllers, then the IBAction will be redundant.
Any suggestion?
You should probably just figure out the device type in applicationDidFinishLaunching and then load separate controllers for each device. But if you want to just have a single implementation for all devices, do checks like this:
if(UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad)
{
//do some iPad stuff
}
else
{
CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
if([UIScreen mainScreen].scale == 2.f && screenH == 568.0f)
{
//do iPhone 5 stuff
}
else
{
//do iPhone 4S and iPhone 4 stuff
//the dimensions are the same however, if you want to do iPhone 4S specific stuff
// you'll need to do additional checks against video resolution or other differences etc
}
}
CGFloat height = [UIscreen mainScreen].bounds.size.height;
if(height==568.00 || height == 480.0)
{
//For iphone 5 and iphone 4
}
else
{
//For ipad
}
You can use this code in your AppDelegate
- (BOOL) isPad()
{
if(UI_USER_INTERFACE_IDIOM)
{
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
}
else
{
return NO;
}
}
This link gives some info about the idiom http://developer.apple.com/library/ios/#documentation/uikit/reference/UIKitFunctionReference/Reference/reference.html#//apple_ref/c/macro/UI_USER_INTERFACE_IDIOM
OR
You can check the height and width of the screen to know whether its an iPhone or iPad
CGRect screen = [[UIScreen mainScreen] bounds];
CGFloat width = CGRectGetWidth(screen);
CGFloat height = CGRectGetHeight(screen);
If you are not using any nib, everything doen programmatically you dont want to create two view controllers for the iphone and ipad. Remember do not depends on any static values. ie your calculations should be made according to self.view.bounds some thing like that (I mean to create views/subviews). Then if some specific functionality that supports only in iPad do checks
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
One view controller done all the job for you.
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 searched every where but not find the solution of this I am New in iphone.In every where I got the set the height of navigation or my view is not rotating in orientation like issue.my view is rotating but my navigation bar is on same position please some one help me if You have solution.Thanks I have show my some code in down which I used for Orientation.when I tap on my tab bar my simulator is automatic rotate and I want tab bar also rotate but using this code only simulator is rotate not tab bar and navigation bar and sorry for my bad english.
CGAffineTransform transform = CGAffineTransformIdentity;
switch ([[UIApplication sharedApplication] statusBarOrientation])
{
case UIInterfaceOrientationPortrait:
transform = CGAffineTransformMakeRotation(M_PI_2);
break;
default:
break;
}
[[UIApplication sharedApplication]setStatusBarOrientation:UIInterfaceOrientationPortrait];
[UIView animateWithDuration:0.2f animations:^ {
[self.navigationController.view setTransform:transform];
}];
[self.view setFrame:CGRectMake(0, 0, 320, 480)];
[self.view setNeedsLayout];
This code is, no offense intended, very curious. I'm not sure what you are trying to do. What problem are you trying to solve? Playing around with CGAffineTransform's can definitely generate strange results like what you describe if you're not very careful.
If you just want to make sure that your app successfully supports landscape and portrait orientations, you can implement shouldAutorotateToInterfaceOrientation in your view controller. When you do this, all of the various controls will reorient themselves accordingly.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Support all orientations on iPad
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
return YES;
// otherwise, for iPhone, support portrait and landscape left and right
return ((interfaceOrientation == UIInterfaceOrientationPortrait) ||
(interfaceOrientation == UIInterfaceOrientationLandscapeLeft) ||
(interfaceOrientation == UIInterfaceOrientationLandscapeRight));
}
But if I have misunderstood what you're trying to do, i.e., you're trying to do something more sophisticated than just supporting both landscape and portrait orientation, let me know.
I apologize because I don't remember where I originally got this code (but it's referenced in SO here), but the following can be used to force landscape orientation:
First, make sure that your shouldAutoRotateToInterfaceOrientation should read as follows:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ((interfaceOrientation == UIInterfaceOrientationLandscapeLeft) ||
(interfaceOrientation == UIInterfaceOrientationLandscapeRight))
return YES;
else
return NO;
}
Second, in viewDidLoad, add the following code:
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation]))
{
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *view = [window.subviews objectAtIndex:0];
[view removeFromSuperview];
[window addSubview:view];
}
For some reason, removing the view from the main window and then re-adding it forces it to query shouldAutorotateToInterfaceOrientation and set the orientation correctly. Given that this isn't an Apple approved approach, maybe one should refrain from using it, but it works for me. Your mileage may vary. But that SO discussion also refers to other techniques, too.
This is more of a general question for people to provide me guidance on, basically Im learning iPad/iPhone development and have finally come across the multi-orientation support question.
I have looked up a fair amount of doco, and my book "Beginning iPhone 3 Development" has a nice chapter on it.
But my question is this, if I was to programatically change my controls (or even use different views for each orientation) how on earth to people maintain their code base? I can just imagine so many issues with spaghetti code/thousands of "if" checks all over the place, that it would drive me nuts to make one small change to the UI arrangement.
Does anyone have experience handling this issue? What is a nice way to control it?
Thanks a lot
Mark
I do this with two simple methods in my view controller:
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self adjustViewsForOrientation:toInterfaceOrientation];
}
- (void) adjustViewsForOrientation:(UIInterfaceOrientation)orientation {
if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
titleImageView.center = CGPointMake(235.0f, 42.0f);
subtitleImageView.center = CGPointMake(355.0f, 70.0f);
...
}
else if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
titleImageView.center = CGPointMake(160.0f, 52.0f);
subtitleImageView.center = CGPointMake(275.0f, 80.0f);
...
}
}
To keep this clean you could easily compartmentalize the view adjustments/reloading/etc. with methods called from inside the single if-else conditional.
It really depends on what it is you are laying out.
If you look at the Apple Settings application, you can see that they use table views for the layout, with custom cells for most rows. With that, you can allow a simple interface to rotate pretty cheaply by just filling the width of the cells. This even applies to things like Mail, where there are edit text cells in each row. And tables can easily be all transparent, with only buttons or labels visible, so they do not look like tables.
You can get a lot of milage out of the autoresizingMask of every UIView. If you have one or more items that can have a flexible height, then you can usually get an interface layout that looks good in either orientation. Depending on how it looks, sometimes you can just pin everything to the top.
In rare cases, if all the interface elements fit in a square, you can just rotate them in place.
There are two times when you must explicitly handle orientation changes. One is when a view moves from beside to below another on rotation. The other is when you have different images for each orientation, for example if you always want to be full width.
There are sometimes ways to work around both of these. You might use stretchable images or limit yourself to one view per line. Or you might lock out orientation for certain views.
If you must change the layout of views, there is an explicit layoutSubviews method. You should try to handle all you conditional layout in this one method. It is only called when the view bounds change, for example on rotation or if you have made room for the keyboard. Make a custom view for each view hierarchy that needs to respond to rotation, and layout the subviews from there.
The iPhone SDK is built around having an MVC architecture, so in theory if you keep all your logic (model) separated from your UI (view) then you will only have to worry about the UI in one spot: your view controllers. For those, you could have a separate view controller for each orientation, each of which would then just be loaded with one if/else to choose which view controller to load.
The same idea holds for iPhone / iPad support, where you can load another view controller which can handle larger displays.
Refer the following link:
http://mustafashaik.wordpress.com/2010/11/17/handling-orientations-in-ipad/
I can't vouch for this code, and in all honesty the above willRotateToInterfaceOrientation works great. Here's another take on it with FBDialog.m from Facebook for iphone / ipad. (albeit, I think this was for a webview)
here's the gist
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(deviceOrientationDidChange:)
name:#"UIDeviceOrientationDidChangeNotification" object:nil];
- (void)deviceOrientationDidChange:(void*)object {
UIDeviceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if ([self shouldRotateToOrientation:orientation]) {
CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
[self sizeToFitOrientation:YES];
[UIView commitAnimations];
}
}
-(CGAffineTransform)transformForOrientation {
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (orientation == UIInterfaceOrientationLandscapeLeft) {
return CGAffineTransformMakeRotation(M_PI*1.5);
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
return CGAffineTransformMakeRotation(M_PI/2);
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
return CGAffineTransformMakeRotation(-M_PI);
} else {
return CGAffineTransformIdentity;
}
}
- (void)sizeToFitOrientation:(BOOL)transform {
if (transform) {
self.transform = CGAffineTransformIdentity;
}
CGRect frame = [UIScreen mainScreen].applicationFrame;
CGPoint center = CGPointMake(
frame.origin.x + ceil(frame.size.width/2),
frame.origin.y + ceil(frame.size.height/2));
CGFloat scale_factor = 1.0f;
if (FBIsDeviceIPad()) {
// On the iPad the dialog's dimensions should only be 60% of the screen's
scale_factor = 0.6f;
}
CGFloat width = floor(scale_factor * frame.size.width) - kPadding * 2;
CGFloat height = floor(scale_factor * frame.size.height) - kPadding * 2;
_orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsLandscape(_orientation)) {
self.frame = CGRectMake(kPadding, kPadding, height, width);
} else {
self.frame = CGRectMake(kPadding, kPadding, width, height);
}
self.center = center;
if (transform) {
self.transform = [self transformForOrientation];
}
}
In your question, you wrote:
I can just imagine so many issues with spaghetti code/thousands of "if" checks all over the place, that it would drive me nuts to make one small change to the UI arrangement.
One way to dodge this is to make a view hierarchy that splits the handling of iPhone/iPad specific changes from the very beginning. You'd only have to set which view is initially loaded for each device. Then you create a viewcontroller like you normally do, but you also subclass the viewcontroller you've created. One subclass for each device. That's where you can put the device specific code, like orientation handling. Like this:
MyViewController.h // Code that is used on both devices
MyViewController_iPhone.h // iPhone specific code, like orientation handling
MyViewController_iPad.h // iPad specific code, like orientation handling
If you are interested in this approach, I'd suggest that you read this article. It explains it in a very nice way.
One of the things the article mentions, is this:
--start of quote--
The beauty of this pattern is we don’t have to litter our code with crap that looks like this:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// The device is an iPad running iPhone 3.2 or later.
// set up the iPad-specific view
} else {
// The device is an iPhone or iPod touch.
// set up the iPhone/iPod Touch view
}
---end of quote--
I hope that helps. Good luck!