I'm developing an app for iPad and I try to handle multiple orientation.
My app contains a webview and a loading UIImageView that appears when my webview is loading content.
This UIImageView has a background image that I set in InterfaceBuilder. When I change orientation to landscape, the image is cut.
I'd like the UIImageView to set image-portrait.png when the ipad is in portrait mode and image-landscape.png when it's in landscape mode.
Thank you for your help and advices!
Screenshots :
I found a solution :
In Interface Builder, I set the autosizing of my ImageView to auto fill the screen.
In my ViewController, I add a method to detect the change of orientation and I set the appropriate image depending if the iPad is in portrait or landscape :
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if((self.interfaceOrientation == UIDeviceOrientationLandscapeLeft) || (self.interfaceOrientation == UIDeviceOrientationLandscapeRight)){
myImageView.image = [UIImage imageNamed:#"image-landscape.png"];
} else if((self.interfaceOrientation == UIDeviceOrientationPortrait) || (self.interfaceOrientation == UIDeviceOrientationPortraitUpsideDown)){
myImageView.image = [UIImage imageNamed:#"image-portrait.png"];
} }
It took me awhile to understand this concept. I didn't want to create the same image portrait and landscape. The key here is that CGAffineTransformMakeRotation rotates from the original state of your UIImageView or any UIView for that matter. This assumes your background image has orientation to it. E.g. You want your UIImageView to stay put, while other objects behaves to normal orientation change event.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
backgroundImage.transform = CGAffineTransformMakeRotation(M_PI / 2);
}
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight){
backgroundImage.transform = CGAffineTransformMakeRotation(-M_PI / 2);
}
else {
backgroundImage.transform = CGAffineTransformMakeRotation(0.0);
}
}
You can handle the orientation by autoresizing the view.
UIImageView *imageView=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"background-image"]];
imageView.frame = self.view.bounds;
imageView.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight
iimageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:imageView];
[self.view sendSubviewToBack:imageView];
[imageView release];
This will be make solution to your problem.
While Salah your answer looks ok to me i believe you can do two improvements here:
Set the background image within this function:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
duration (NSTimeInterval)duration
if you do the change within the didRotateFromInterfaceOrientation
you will change the background image once you have finished to
rotate the IPad and the transition from the two background image
won't be smooth: you will clearly see the new background image popup
at the end of the rotation.
Improve setting the myImageView.image value:
_myImageView.image = UIInterfaceOrientationIsLandscape(interfaceOrientation) ? [UIImage
imageNamed:#"image-landscape.png"] : [UIImage
imageNamed:#"image-portrait.png"];
I'd actually add another branch to docchang's code as when the iPad is rotated to portrait upside own it uses the portrait right-side-up image which can look a little odd.
I added,
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
imageBackgroundView.transform = CGAffineTransformMakeRotation(M_PI / 2);
}
else
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
imageBackgroundView.transform = CGAffineTransformMakeRotation(-M_PI / 2);
}
else
if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
imageBackgroundView.transform = CGAffineTransformMakeRotation(M_PI);
}
else
{
imageBackgroundView.transform = CGAffineTransformMakeRotation(0);
}
It is interesting that programmers have such a tough time thinking outside the box (literally in this case).
Try this (how I solved this myself).
Create a square image.
Set the constraints of the uiimageview to fill in the screen (leading, trailing, top, bottom)
Set the mode to aspect fill (which will enlarge the image, but keep its aspect ratio constant)
Done.
The key here is, of course, that you should create a square image (which is, as I said above, outside the box ;-)
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.
I have UIScrollView on xib-file on portrait mode. When I run the application and rotate device to landscape mode I see the new bounds of the UIScrollView. Half of the controls inside UIScrollView at the bottom are inaccessible. There is no any scroller visible.
How to prevent change of the vertical size of UIScrollView after device orientation is changed?
Thanks a lot for the help!
Change the UIScrollView's frame in
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
According to the device rotation.
shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if(interfaceOrientation == UIInterfaceOrientationPortrait)
{
//set frame for scroll in portrait mode
}
else if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
//set frame for landscape
}
}
select your UIScrollView and then from properties click on size inspector and change autoresize property for your view.
EDIT : select your UIScrollView and change autoresize property for your scroll view like this.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
{
//change sub view of scrollview accordingly to orientation
if(UIInterfaceOrientationIsPortrait(interfaceOrientation))
yourScrollView.frame = CGRectMake(0,0,320,460);
yourScrollView.contentSize = CGSizeMake(320,460);//change accordingly
else
yourScrollView.frame = CGRectMake(0,0,480,300);
yourScrollView.contentSize = CGSizeMake(480,460); //change accordingly
return YES;
}
Setup Reference Outlet.
Use the code:
scrollView.contentSize = CGSizeMake(width, height);
I am writing an app that I would like to only be viewed in Landscape Mode. I have set it up so that if the iPhone is held in Portrait Mode, nothing happens and the current image remains in Landscape Mode. The iPhone Simulator starts out in Landscape Mode with the Home Button on the right. If the iPhone is rotated from one Landscape Mode to the other, animation then occurs and the view is adjusted. However, whenever the device is in Landscape Mode with the Home Button on the left, the image is 20 pixels higher than needed, revealing a white line at the bottom of the screen.
In spite of all attempts to logically adjust this such as
self.view.frame = CGRectMake (0,20, self.view.frame.size.width, self.view.frame.size.height)
it doesn't fix the problem. I am accounting for the Status Bar in my calculations.
The .xib file contains an UIImageView on top of a UIView. This is my first experience implementing these methods so I apologize if the solution is relatively easy. Below is the code for the two methods used to implement the Landscape Mode views.
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
//set up interface to only be viewed in Landscape
if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
return interfaceOrientation;
else if(interfaceOrientation == UIInterfaceOrientationLandscapeRight)
return interfaceOrientation;
else
return NO;
}
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)tointerfaceOrientation duration:(NSTimeInterval)duration {
if(UIInterfaceOrientationLandscapeRight){
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
else if (UIInterfaceOrientationLandscapeLeft) {
//shouldn't adjustment to 20 fix the view?
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
else return;
}
For UIViews to adjust the subviews properly you will have to take a look at this UIView property: autoresizingMask, and set the mask to autoresize what is needed, if that does not work you will have to override:
- (void)layoutSubviews
From the reference: UIView reference
I'm using setting the background image using methodology below. When I rotate my device the background repeats, which make sense because it is not an image. How do I deal with orientation change if this is the way I'm setting my background image?
- (void)viewDidLoad {
[super viewDidLoad];
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"background.png"]];
self.view.backgroundColor = background;
[background release];
}
It took me awhile to understand this concept. I didn't want to create the same image portrait and landscape. The key here is that CGAffineTransformMakeRotation rotates from the original state of your UIImageView or any UIView for that matter. This assumes your background image has orientation to it. E.g. You want your UIImageView to stay put, while other objects behaves to normal orientation change event.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
backgroundImage.transform = CGAffineTransformMakeRotation(M_PI / 2);
}
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight){
backgroundImage.transform = CGAffineTransformMakeRotation(-M_PI / 2);
}
else {
backgroundImage.transform = CGAffineTransformMakeRotation(0.0);
}
}
- (void)viewDidLoad {
[super viewDidLoad];
//set the UIImageView to current UIView frame
backgroundImage.frame = self.view.frame;
}
You have to take 2 images both for horizontal and vertical and instead of allocating you can use [...: colorWithPatternImage:...]; and set it when orientation is changed to the background of the view.
hAPPY iCODING...
If I understand correctly, Your background gets created or overwritten every time you change the orientation right. By default backgroundColor is nil. You can check for this, if it is nil then you go ahead and set the values.
Its like
if ( self.view.backgroundColor == nil){
//set the new values here
}
I have an UIImage inside an UIImageView in my application.
In portrait mode, the image is centered but when I switch to the landscape mode it stills on the left.
So I added the following method in my .m file but the problem is that I have a TabBar application, so when I rotate the device on another tab and I go back to the tab containing the image, it doesn't rotate automatically.
There is a way to rotate automatically all the elements of the application when rotating the device on any tab ?
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if((self.interfaceOrientation == UIDeviceOrientationLandscapeLeft) || (self.interfaceOrientation == UIDeviceOrientationLandscapeRight)){
background.image = [UIImage imageNamed:#"back2-landscape.png"];
} else if((self.interfaceOrientation == UIDeviceOrientationPortrait) || (self.interfaceOrientation == UIDeviceOrientationPortraitUpsideDown)){
background.image = [UIImage imageNamed:#"back2-portrait.png"];
}
}
Thank you :-)
There are two options:
*1. Do in in IB - Turn off all those little spaces and arrows in the "Autosizing" area under the "Size Inspector". You should be able to then "Simulate Interface", and rotate the [simulated] device, with the object staying centered.
*2. Do it programmatically, in didRotateFromInterfaceOrientation by doing something like:
[theObject setCenter:self.center];