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.
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 have one app always presenting in Portrait mode (in the summary of the Xcode project, only the portrait orientation is supported).
Now what I want to do is when I'm using the app, from any view controllers of the app, if I rotate the device in landscape right, the app presents a view controller (ARViewController.m) in landscape right, in fact the rotation to landscape right is the trigger to present ARViewController.m. But what I've experienced is, since the the first view controller only supports portrait and even if I orient the device in landscape right, the view controller (ARViewController.m) I want to present from the first one is in portrait too, not in landscape right.
Even if I write this in the second view controller (ARViewController.m), it doesn't autorotate (this view controller can be presented in every orientations):
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
I have to rotate the iphone once after the second view controller (ARViewController.m) is presented to have all in order.
And here is how I call this second view controller (ARViewController.m) from the first view controller:
ARViewController *arVC = [[ARViewController alloc] initWithNibName:#"ARViewController" bundle:nil];
[self presentModalViewController:arVC animated:YES];
I'm calling it from "ViewController.m", this one is defined as the rootViewController in the AppDelegate.m.
This is the first time I'm doing such things, I've looked for solutions but still the same problem. Any tips on this?
I finally solved this problem, I suppose there are alternatives but this one works fine:
In fact I kept only Portrait in the orientation restrictions. Then when I turn the phone in landscape right or left, I call my ARViewController modally, but before loading it I force this view controller to landscape (in viewWillAppear) by making an appropriate rotation like here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self transformView2ToLandscape];}
-(void) transformView2ToLandscape {
NSInteger rotationDirection;
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
if(currentOrientation == UIDeviceOrientationLandscapeLeft){
rotationDirection = 1;
}else {
rotationDirection = -1;
}
CGAffineTransform transform = [arController.viewController.view transform];
transform = CGAffineTransformRotate(transform, degreesToRadians(rotationDirection * 90));
[arController.viewController.view setTransform: transform];}
Edit: In Swift 4
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
transformViewToLansdcape()
}
func transformViewToLansdcape(){
var rotationDir : Int
if(UIDeviceOrientationIsLandscape(UIDevice.current.orientation)){
rotationDir = 1
}else{
rotationDir = -1
}
var transform = self.view.transform
//90 for landscapeLeft and 270 for landscapeRight
transform = transform.rotated(by: (rotationDir*270).degreesToRadians)
self.view.transform = transform
}
extension BinaryInteger {
var degreesToRadians: CGFloat {
return CGFloat(Int(self)) * .pi / 180
}
}
What i've found is that first of all, what's stopping the entire application from rotating is the project summary sheet. What you should do is deselect the restrictions in the project summary sheet & just put a method in each UIViewController like you'd like it to be. In the UIViewController that you have, make the method available for the landscape orientations & implement the method
[UIViewControllerSubclass willRotateToInterfaceOrientation:<interfaceOrientation> duration:<NSTimeInterval>]
what Apple says:
willRotateToInterfaceOrientation:duration:
Sent to the view controller just before the user interface begins rotating.
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Parameters
toInterfaceOrientation
The new orientation for the user interface. The possible values are described in
UIInterfaceOrientation.
duration
The duration of the pending rotation, measured in seconds.
Discussion
Subclasses may override this method to perform additional actions immediately prior to the rotation. For example, you might use this method to disable view interactions, stop media playback, or temporarily turn off expensive drawing or live updates. You might also use it to swap the current view for one that reflects the new interface orientation. When this method is called, the interfaceOrientation property still contains the view’s original orientation.
This method is called regardless of whether your code performs one-step or two-step rotations.
so, this seems like the method you are looking for. Just implement this, and put your view calling code inside & it should work. (also, put one of these in the presented view to return when rotated back)
also, you should consider not presenting the view controller modally as it is rotating anyways and is obvious that the display is changing.
Ended up with doing this:
Easiest way to support multiple orientations? How do I load a custom NIB when the application is in Landscape? Works superb!
I have made an image in Photoshop which I want to use as background for my info-screen in my iPad application. The image contains text and some icons also. Around the image I have a border which is green.
The effect I am trying to achieve is:
When the user goes from the portrait orientation to landscape orientation I want the image (just the frame and the icons) to rotate 90 degrees so the image appear in landscape mode, instead of having a portrait view of the frame in landscape. The text and icons are decoupled (different layers which I have organized in different UIImageView's)they shall rotate 90 degrees.
What I have done already is the following:
Experimented a bit with this method:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:
and tried to do this:
self.btnFacebook.transform = CGAffineTransformMakeRotation(1.5707964);
which I think will rotate the btnFacebook property 90 degrees to the right (correct me if I am wrong) since it specifies positive radians. I can't seem to get it to work correctly though. Should'nt this rotate the button 90 degrees around its center coordinate in the frame? That wouldn't cause a change in the position of the button (the button is square)?
EDIT
Made an image:
As the image shows the image background (some custom graphics on it which looks good in both orientations) goes from portrait to landscape and rotates so it does not appear as portrait in landscape, the icons are decoupled from the background so they need to rotate as well because they need to be in the right orientation (it is social icons). The text however are on the same position, it only rotates 90 degrees without repositioning.
I understand your question as that you are trying to not to rotate the interface as whole but to rotate the purple and red squares individually.
I created a UIViewController that resembles your layout.
The black square is a UIView and the white square is there only so that I can tell when the black view rotates. This view is wired to view1 property on the controller.
There are 4 buttons that are wired to btnx (x runs 1 through 4) properties.
Since I no longer want to auto rotate the interface I only support the portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
To do the rotation manually, I added a method to the ViewController. It determines the angle it needs to rotate the components from portrait to current orientation, creates a rotation transform and applies it to all outlets.
- (void)deviceDidRotate:(NSNotification *)notification
{
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
double rotation = 0;
UIInterfaceOrientation statusBarOrientation;
switch (currentOrientation) {
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationUnknown:
return;
case UIDeviceOrientationPortrait:
rotation = 0;
statusBarOrientation = UIInterfaceOrientationPortrait;
break;
case UIDeviceOrientationPortraitUpsideDown:
rotation = -M_PI;
statusBarOrientation = UIInterfaceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeLeft:
rotation = M_PI_2;
statusBarOrientation = UIInterfaceOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeRight:
rotation = -M_PI_2;
statusBarOrientation = UIInterfaceOrientationLandscapeLeft;
break;
}
CGAffineTransform transform = CGAffineTransformMakeRotation(rotation);
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self.btn1 setTransform:transform];
[self.btn2 setTransform:transform];
[self.btn3 setTransform:transform];
[self.btn4 setTransform:transform];
[self.view1 setTransform:transform];
[[UIApplication sharedApplication] setStatusBarOrientation:statusBarOrientation];
} completion:nil];
}
The last thing to do is to get the OS to call my method. To achieve that I added the following code to application:didFinishLaunchingWithOptions: in the AppDelegate.
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self.viewController selector:#selector(deviceDidRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
I am not sure whether this is exactly what you wanted but I believe it is at least similar so you can get some ideas from it how to solve your problem. I can provide source code for a working iPad application that I created to illustrate this.
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!