when a user makes a screenshot with the press of homebutton and powerbutton than is the screenshot upside down. The device does never autorotate. I observe UIDeviceOrientationDidChangeNotification and if it is in landscape, I will show the rotated view. Depending on left or right device orientation
This happens when I show a view that is rotated with this code
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_TO_RADIANS(90));
landscape.transform = rotationTransform;
Here are the rotation methods of the viewcontroller
- (BOOL)shouldAutorotate {
return NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return NO;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
I disable autorotate and force the rotation:
- (void)forceOrientationChange {
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
switch (deviceOrientation) {
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationPortrait:
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
break;
case UIDeviceOrientationLandscapeLeft: {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:NO];
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_TO_RADIANS(90));
landscape.transform = rotationTransform;
break;
}
case UIDeviceOrientationLandscapeRight: {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_TO_RADIANS(-90));
landscape.transform = rotationTransform;
break;
}
default:
break;
}
}
I just switched UIInterfaceOrientationLandscapeLeft to UIInterfaceOrientationLandscapeRight
- (void)forceOrientationChange {
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
switch (deviceOrientation) {
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationPortrait:
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
break;
case UIDeviceOrientationLandscapeLeft: {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_TO_RADIANS(90));
landscape.transform = rotationTransform;
break;
}
case UIDeviceOrientationLandscapeRight: {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:NO];
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_TO_RADIANS(-90));
landscape.transform = rotationTransform;
break;
}
default:
break;
}
}
Related
My application is only supported on Portrait mode. I have requirement that need to rotate the particular UIView only according to the device orientation.
Can you please help me?
if (interfaceOrientation == UIInterfaceOrientationPortrait) {
// set your view frame work for portrait mode
}
else {
// set new frame of view for landscape mode.
}
Hope you get the idea.
thanks
Add below give code in your viewdidload or init
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(deviceOrientationDidChange:) name: UIDeviceOrientationDidChangeNotification object: nil];
In deviceOrientationDidChange function you will get the device orientation,according to this you can update your view
- (void)deviceOrientationDidChange:(NSNotification *)notification {
//Obtain current device orientation
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
//Do my thing
}
After the long try I found the answer using CGAffineTransform ..
- (void)deviceOrientationDidChange:(NSNotification *)notification {
//Obtain current device orientation
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
CGFloat angle;
switch (orientation) {
case UIDeviceOrientationPortraitUpsideDown:
angle = M_PI;
break;
case UIDeviceOrientationLandscapeRight:
angle = - M_PI_2;
break;
case UIDeviceOrientationLandscapeLeft:
angle = M_PI_2;
break;
case UIDeviceOrientationPortrait:
default:
angle = 0.f;
break;
}
animationView.transform= CGAffineTransformMakeRotation(angle);
animationView.frame = self.view.frame;
}
I'm trying to add a UIWindow with UIWindowLevelStatusBar level. it works in portrait mode on ipad perfectly but after rotating device its messed up.
on iOS 7.x all rotations are fine except for upside-down, and on iOS 8.x only portrait mode is fine and all other orientations are messed up. any idea how to solve that?
CGRect frame = [UIApplication sharedApplication].statusBarFrame;
self.statusWindow = [[UIWindow alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 20)];
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect frame = [UIApplication sharedApplication].statusBarFrame;
CGAffineTransform test = [self transformForOrientation:orientation];
[self.statusWindow setWindowLevel:UIWindowLevelStatusBar];
[self.statusWindow setHidden:NO];
- (CGAffineTransform)transformForOrientation:(UIInterfaceOrientation)orientation
{
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
return CGAffineTransformMakeRotation(-DEGREES_TO_RADIANS(90.0f));
case UIInterfaceOrientationLandscapeRight:
return CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(90.0f));
case UIInterfaceOrientationPortraitUpsideDown:
return CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(180.0f));
case UIInterfaceOrientationPortrait:
default:
return CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0.0f));
}
}
In your UIWindow subclass' init method, observe this notification:
// Rotation Notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didChangeOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
self.baseT = self.transform;
Then the method itself:
- (void)didChangeOrientation:(NSNotification *)n
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
switch (orientation) {
case UIInterfaceOrientationPortrait:
self.transform = CGAffineTransformRotate(self.baseT, 0);
break;
case UIInterfaceOrientationPortraitUpsideDown:
self.transform = CGAffineTransformRotate(self.baseT, M_PI);
break;
// Home button on left
case UIInterfaceOrientationLandscapeLeft:
self.transform = CGAffineTransformRotate(self.baseT, -M_PI_2);
break;
case UIInterfaceOrientationLandscapeRight:
self.transform = CGAffineTransformRotate(self.baseT, M_PI_2);
break;
default:
self.transform = CGAffineTransformMakeRotation(0);
break;
}
}
Depending on your implementation, you may have to also ensure the frame doesn't change. Also, in the Class' dealloc method, stop listening to the notifications.
As a modification to #dezinezync's answer: performing the rotation would work better if you put it in the willRotateToInterfaceOrientation method of the view controller. This way, the rotation will only be applied if the device is rotated to one of the supported orientations specified in the General -> Deployment Info section of the app's build settings. As an added bonus, you get access to the duration of the rotation animation. Just make sure you have a reference to your modal window in the view controller.
The UIDeviceOrientationDidChangeNotification notification is fired whenever the screen orientation changes, regardless of what orientations are supported. This can lead to undesireable behavior.
Here's an example:
UIWindow *modalWindow;
// Initialize your modal window somewhere in your code
// ...
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
// Make sure the modalWindow exists
if (modalWindow != nil)
{
// Animate the modal window's rotation
[UIView animateWithDuration:duration animations:^{
[self rotateModal:modalWindow];
}];
}
}
- (void)rotateModal:(UIWindow*)window
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
switch (orientation) {
case UIInterfaceOrientationPortrait:
window.transform = CGAffineTransformRotate(self.baseT, 0);
break;
case UIInterfaceOrientationPortraitUpsideDown:
window.transform = CGAffineTransformRotate(self.baseT, M_PI);
break;
// Home button on left
case UIInterfaceOrientationLandscapeLeft:
window.transform = CGAffineTransformRotate(self.baseT, -M_PI_2);
break;
case UIInterfaceOrientationLandscapeRight:
window.transform = CGAffineTransformRotate(self.baseT, M_PI_2);
break;
default:
window.transform = CGAffineTransformMakeRotation(0);
break;
}
}
I just did something like this on a landscape-only app, and I finally got it working properly by moving everything to the willRotateToInterfaceOrientation method.
Solution for iOS8 (need a transform as showed above as well):
UIScreen *screen = [UIScreen mainScreen];
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if ([screen respondsToSelector:#selector(fixedCoordinateSpace)])
{
[self setFrame:[screen.coordinateSpace convertRect:statusBarFrame toCoordinateSpace:screen.fixedCoordinateSpace]];
}
However it does not work for iOS9 beta.
I am using an UIImagePickerController within an UIPopoverController which is working perfectly with iOS6. With iOS 7 the "preview" image which is shown to capture the image is rotated, but if I take a picture it is saved correctly.
This is how I get my picker:
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.mediaTypes = [NSArray arrayWithObjects:
(NSString *) kUTTypeImage,
nil];
imagePicker.allowsEditing = NO;
And add it to a popover controller:
self.imagePickerPopOver = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
[self.imagePickerPopOver presentPopoverFromRect:CGRectMake(aPosViewA.x, cameraButton_y, 100.0, 30.0) inView:self.detailViewController.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Those are calculations for the button position at a UIScrollView to show the popover at the correct position:
presentPopoverFromRect:CGRectMake(aPosViewA.x, cameraButton_y, 100.0, 30.0)
I don't think that the problem lies there as I have tried out several combinations.
I have also tried to capture the image in fullscreen-mode, but the app is only allowed to use landscape mode. If the image is taken in portrait-mode and the modal view is dismissed, the app stays in portrait mode as well. I couldn't find a way to prevent the UIImagePickerController to switch to portrait mode or to force the app back to landscape mode if the modal view was dismissed.
UPDATE
I have taken the answer from here and came a step further.
I transform the view after creating the picker and before showing the popover :
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
self.imagePicker.view.transform = CGAffineTransformMakeRotation(M_PI/2);
break;
case UIInterfaceOrientationLandscapeRight:
self.imagePicker.view.transform = CGAffineTransformMakeRotation(-M_PI/2);
break;
default:
break;
}
which works as long as i don't turn around the iPad. For that I am registering for the orientation changed event:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
and change the picker view:
- (void)orientationChanged:(NSNotification *)notification{
if (self.imagePicker) {
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
self.imagePicker.view.transform = CGAffineTransformMakeRotation(M_PI/2);
break;
case UIInterfaceOrientationLandscapeRight:
self.imagePicker.view.transform = CGAffineTransformMakeRotation(-M_PI/2);
break;
default:
break;
}
}
}
REMAINING PROBLEM:
As I wrote in the beginning, when the picture was taken, it was shown correctly to accept or dismiss it. This is now transformed as well. Somehow I need to know when the image is taken and transform it back.
AND, this is really a nasty hack and probably won't work with the next iOS Update. Has anybody an idea how to implement that in a cleaner way?
UPDATE 2
This was too nasty, I have found a cleaner solution which solves my problem but is not the answer to the initial question regarding an imagepicker in a popover controller, which is not recommended by Apple, but allowed.
I have subclassed now the UIImagePickerController like this:
#implementation QPImagePickerController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscape;
}
#end
and I am using the imagepicker in fullscreen instead in a popover. Tested so far in iOS7.
The UIImagePickerController has a property called cameraViewTransform. Applying a CGAffineTransform to this will transform the preview image but will not transform the captured image which will therefore be correctly captured. I have the same problem that you describe and I solved it (for iOS7) by creating my camera controller and placing it in a popover as follows:
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
[imagePickerController setDelegate:self];
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
CGFloat scaleFactor=1.3f;
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
imagePickerController.cameraViewTransform = CGAffineTransformScale(CGAffineTransformMakeRotation(M_PI * 90 / 180.0), scaleFactor, scaleFactor);
break;
case UIInterfaceOrientationLandscapeRight:
imagePickerController.cameraViewTransform = CGAffineTransformScale(CGAffineTransformMakeRotation(M_PI * -90 / 180.0), scaleFactor, scaleFactor);
break;
case UIInterfaceOrientationPortraitUpsideDown:
imagePickerController.cameraViewTransform = CGAffineTransformMakeRotation(M_PI * 180 / 180.0);
break;
default:
break;
}
__popoverController = [[UIPopoverController alloc] initWithContentViewController:imagePickerController];
[__popoverController presentPopoverFromRect:presentationRect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
I also scale the image when in Landscape so that it fills the viewfinder more than it otherwise would. To my mind this is all rather nasty, but I will hopefully be able to remove it once iOS7.1 arrives.
I've found another solution which handles the case where a device is rotated while the UIIMagePickerView is presenting based on the answer Journeyman provided. It also handles the case where the view is rotated from UIOrientationLandscapeRight/UIOrientationLandscapeLeft back to UIOrientationPortrait.
I created a subclass of UIImagePickerController:
#import <UIKit/UIKit.h>
#interface PMImagePickerController : UIImagePickerController
#end
Then registered it to receive notifications if the device orientation changes:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(fixCameraOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
The selector fixCameraOrientation contains Journeyman's code with one extra case, wrapped in a check to make sure the sourceType is the camera:
- (void)fixCameraOrientation:(NSNotification*)notification
{
if (self.sourceType == UIImagePickerControllerSourceTypeCamera) {
CGFloat scaleFactor=1.3f;
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
self.cameraViewTransform = CGAffineTransformScale(CGAffineTransformMakeRotation(M_PI * 90 / 180.0), scaleFactor, scaleFactor);
break;
case UIInterfaceOrientationLandscapeRight:
self.cameraViewTransform = CGAffineTransformScale(CGAffineTransformMakeRotation(M_PI * -90 / 180.0), scaleFactor, scaleFactor);
break;
case UIInterfaceOrientationPortraitUpsideDown:
self.cameraViewTransform = CGAffineTransformMakeRotation(M_PI * 180 / 180.0);
break;
case UIInterfaceOrientationPortrait:
self.cameraViewTransform = CGAffineTransformIdentity;
break;
default:
break;
}
}
}
The important case here is the case where the device orientation goes to portrait. The overlay's view needs to be reset in this case. It's also important for the fixCameraOrientation selector to be called after the image picker view loads, in case the device is rotated:
- (void)viewDidLoad
{
[super viewDidLoad];
[self fixCameraOrientation:nil];
}
I had a similar situation in my app. However the preview was rotating correctly in iOS7 and not in iOS8. This code assumes you have more than one orientation.
The first thing is to subclass UIImagePickerController.
Starting from the top, add #import <AVFoundation/AVFoundation.h> to your .m file.
Also add a property to save the initial orientation #property (nonatomic) UIInterfaceOrientation startingOrientation; and another for a condition to remove clipping #property (nonatomic) BOOL didAttemptToRemoveCropping;.
Were going to listen to a couple of notifications.
UIApplicationDidChangeStatusBarOrientationNotification is obviously to listen for the device rotation. AVCaptureSessionDidStartRunningNotification is called right when the camera starts capturing.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(captureSessionDidStart:) name:AVCaptureSessionDidStartRunningNotification object:nil];
In the -captureSessionDidStart: add a condition to verify the view is actually on screen and to make sure the camera is supposed to be displayed if (self.view.window && self.sourceType == UIImagePickerControllerSourceTypeCamera). If so, set the starting orientation self.startingOrientation = [UIApplication sharedApplication].statusBarOrientation;.
In the -statusBarOrientationDidChange: add the same condition as above, but this time if true, we'll update the camera transform. First we get the offset rotation based on the initial rotation. This is needed when you enter the UIImagePickerController in orientations other then portrait.
CGFloat startingRotation = ({
CGFloat rotation;
switch (self.startingOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
rotation = M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
rotation = -M_PI_2;
break;
case UIInterfaceOrientationLandscapeRight:
rotation = M_PI_2;
break;
default:
rotation = 0.0f;
break;
}
rotation;
});
Next we'll update the camera transform with the current rotation.
self.cameraViewTransform = CGAffineTransformMakeRotation(({
CGFloat angle;
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
angle = startingRotation + M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
angle = startingRotation + M_PI_2;
break;
case UIInterfaceOrientationLandscapeRight:
angle = startingRotation + -M_PI_2;
break;
default:
angle = startingRotation;
break;
}
angle;
}));
And finally we'll attempt to remove the black bars presented in either 90 degree orientation from the starting orientation. (This might only be an iOS8 issue.) In slightly more detail, if I enter the UIImagePickerController in portrait mode and then rotate to landscape, there will be black bars on the top and bottom of the preview. The solution for this is not to scale but rather to remove the clipping of a superview. We only need to make this attempt once, so first check if we have called this code. Also make sure that we only call this code if we have rotated. If its called in the initial orientation it wont work right away.
if (!self.didAttemptToRemoveCropping && self.startingOrientation != [UIApplication sharedApplication].statusBarOrientation) {
self.didAttemptToRemoveCropping = YES;
[self findClippedSubviewInView:self.view];
}
Finally in the code for -findClippedSubviewInView: we loop through all the subviews to search for a view with .clipsToBounds = YES. If thats true we make one more condition to verify one of its ancestral superviews is correct.
for (UIView* subview in view.subviews) {
if (subview.clipsToBounds) {
if ([self hasAncestorCameraView:subview]) {
subview.clipsToBounds = NO;
break;
}
}
[self findClippedSubviewInView:subview];
}
In the -hasAncestorCameraView: we simply loop up the superview chain and return true if one of the classes has CameraView in the name.
if (view == self.view) {
return NO;
}
NSString* className = NSStringFromClass([view class]);
if ([className rangeOfString:#"CameraView"].location != NSNotFound) {
return YES;
} else {
return [self hasAncestorCameraView:view.superview];
}
Thats the breakdown of code, here's it all together.
#import <AVFoundation/AVFoundation.h>
#import "GImagePickerController.h"
#interface GImagePickerController ()
#property (nonatomic) UIInterfaceOrientation startingOrientation;
#property (nonatomic) BOOL didAttemptToRemoveCropping;
#end
#implementation GImagePickerController
- (instancetype)init {
self = [super init];
if (self) {
if ([[[UIDevice currentDevice] systemVersion] intValue] >= 8) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(captureSessionDidStart:) name:AVCaptureSessionDidStartRunningNotification object:nil];
}
}
return self;
}
- (void)dealloc {
if ([[[UIDevice currentDevice] systemVersion] intValue] >= 8) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
}
#pragma mark - Capture Session
- (void)captureSessionDidStart:(NSNotification *)notification {
if (self.view.window && self.sourceType == UIImagePickerControllerSourceTypeCamera) {
[self updateStartingOrientation];
}
}
#pragma mark - Orientation
- (void)updateStartingOrientation {
self.startingOrientation = [UIApplication sharedApplication].statusBarOrientation;
[self updateCameraTransform];
}
- (void)updateCameraTransform {
CGFloat startingRotation = ({
CGFloat rotation;
switch (self.startingOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
rotation = M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
rotation = -M_PI_2;
break;
case UIInterfaceOrientationLandscapeRight:
rotation = M_PI_2;
break;
default:
rotation = 0.0f;
break;
}
rotation;
});
self.cameraViewTransform = CGAffineTransformMakeRotation(({
CGFloat angle;
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
angle = startingRotation + M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
angle = startingRotation + M_PI_2;
break;
case UIInterfaceOrientationLandscapeRight:
angle = startingRotation + -M_PI_2;
break;
default:
angle = startingRotation;
break;
}
angle;
}));
if (!self.didAttemptToRemoveCropping && self.startingOrientation != [UIApplication sharedApplication].statusBarOrientation) {
self.didAttemptToRemoveCropping = YES;
[self findClippedSubviewInView:self.view];
}
}
- (void)statusBarOrientationDidChange:(NSNotification *)notification {
if (self.view.window && self.sourceType == UIImagePickerControllerSourceTypeCamera) {
[self updateCameraTransform];
}
}
#pragma mark - Remove Clip To Bounds
- (BOOL)hasAncestorCameraView:(UIView *)view {
if (view == self.view) {
return NO;
}
NSString* className = NSStringFromClass([view class]);
if ([className rangeOfString:#"CameraView"].location != NSNotFound) {
return YES;
} else {
return [self hasAncestorCameraView:view.superview];
}
}
- (void)findClippedSubviewInView:(UIView *)view {
for (UIView* subview in view.subviews) {
if (subview.clipsToBounds) {
if ([self hasAncestorCameraView:subview]) {
subview.clipsToBounds = NO;
break;
}
}
[self findClippedSubviewInView:subview];
}
}
#end
iPad with Camera - don't display in a popover. Instead, present in a modal view controller, like you would on the iPhone. (at least starting with iOS 7)
I am developing an iOS application in which I need to rotate 6 UIViews so they are always "facing" the user. The rest of the interface doesn't need to be rotated.
The code below was working amazingly in the Simulator. However when I tested on my phone I faced a problem. Whenever I put the phone in a flat origination/horizontal, it understand it should go back to portrait. Any idea what this is happening and a work around?
#pragma mark Rotation
- (BOOL)shouldAutorotate
{
return NO;
}
- (void)orientationChanged:(NSNotification *)notification
{
CGFloat rotationAngle = [self convertOrientationToRadians];
[UIView animateWithDuration:0.5 animations:^{
for (TimerButton *timerButton in self.timerButtons) {
timerButton.button.transform = CGAffineTransformMakeRotation(rotationAngle);
}
}];
}
- (CGFloat)convertOrientationToRadians
{
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
CGFloat rotationAngle = 0.0;
switch (orientation) {
case UIDeviceOrientationPortrait:
rotationAngle = 0.0;
break;
case UIDeviceOrientationLandscapeLeft:
rotationAngle = M_PI / 2;
break;
case UIDeviceOrientationPortraitUpsideDown:
rotationAngle = M_PI;
break;
case UIDeviceOrientationLandscapeRight:
rotationAngle = - M_PI / 2;
break;
default:
break;
}
return rotationAngle;
}
Your default value for rotation angle is 0, and the case UIDeviceOrientationFaceUp is not explicitly handled.
Alternatively, you could call [[UIApplication sharedApplication] statusBarOrientation] rather than [UIDevice currentDevice].orientation.
Summary:
I can't force the CALayer to respond correctly to orientation changes.
Whenever I try to use cgaffinetransform I am getting weird results (layer is not centered).
Any help will be appreciated!
thanks,
Process
I am adding a preview of video using AVCaptureVideoPreviewLayer subclass. When device is in a portrait orientation everything looks fine. The problem appears when device is rotated to landscape orientation (left or right) or portrait upside down.
I am adding a preview of video using AVCaptureVideoPreviewLayer subclass. When device is in a portrait orientation everything looks fine. The problem appears when device is rotated to landscape orientation (left or right) or portrait upside down.
I am adding a preview layer using the following code:
CGRect layerRect = [[[self view] layer] bounds];
[[[self captureManager] previewLayer] setBounds:layerRect];
[[[self captureManager] previewLayer]setFrame:CGRectMake(0, height, width, height)];
[[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
And it is displayed properly in portrait mode.
When I try to rotate the device, preview layer behaves weird. It seems like it doesn't resize itself, and it doesn't rotate correctly.
I tried to fix it by adding the following method
-(void)rotateLayer{
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];
switch (orientation) {
case UIDeviceOrientationLandscapeLeft:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress
NSLog(#"Landscape Left");
[stuckview setPosition: CGPointMake(self.view.bounds.size.width /2.0, self.view.bounds.size.height /2.0)];
break;
case UIDeviceOrientationLandscapeRight:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
NSLog(#"Landscape Right");
[stuckview setPosition: CGPointMake(self.view.bounds.size.width /2.0, self.view.bounds.size.height /2.0)];
break;
case UIDeviceOrientationPortraitUpsideDown:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
NSLog(#"Landscape Upside down");
break;
default:
stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
break;
}
float h1 = stuckview.frame.size.height;
float w1 = stuckview.frame.size.width;
if(UIDeviceOrientationIsPortrait(orientation))
{
stuckview.position =CGPointMake(h1/2.0, w1/2.0);
NSLog(#"Portrait");
}
else{
stuckview.position =CGPointMake(w1/2.0, h1/2.0);
NSLog(#"Portrait");
}
}
After adding the method above I can see a progress. Now layer rotates correctly reflecting current device orientation and it's displayed correctly in landscape mode, but NOT in portrait mode.
The layer is not positioned correctly, it isn't centered on the screen (look at the screenshot). To see what's happening I added following debug statements:
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
float h = stuckview.bounds.size.height;
float w = stuckview.bounds.size.width;
float x = stuckview.bounds.origin.x;
float y = stuckview.bounds.origin.y;
float h1 = stuckview.frame.size.height;
float w1 = stuckview.frame.size.width;
float x1 = stuckview.frame.origin.x;
float y1 = stuckview.frame.origin.y;
NSLog(#"%f %f %f %f ", h,w,x,y );
NSLog(#"%f %f %f %f ", h1,w1,x1,y1 );
NSLog(#"Anchor Point: %f %f",stuckview.anchorPoint.x, stuckview.anchorPoint.y);
NSLog(#"Position: %f %f",stuckview.position.x, stuckview.position.y);
CGAffineTransform at = stuckview.affineTransform;
NSLog(#"Affine Transform After : %f %f %f %f %f %f %f", at.a,at.b, at.c, at.d, at.tx,at.tx, at.ty);
And get the following output:
2012-09-30 13:25:12.067 RotatePreviewLayer[2776:907] 1024.000000 768.000000 0.000000 0.000000
2012-09-30 RotatePreviewLayer[2776:907] 1024.000000 768.000000 128.000000 -128.000000
2012-09-30 13:25:12.070 RotatePreviewLayer[2776:907] Portrait
2012-09-30 13:25:12.072 RotatePreviewLayer[2776:907] Anchor Point: 0.500000 0.500000
2012-09-30 13:25:12.074 RotatePreviewLayer[2776:907] Position: 512.000000 384.000000
2012-09-30 13:25:12.076 RotatePreviewLayer[2776:907] Affine Transform after: -1.000000 0.000000 -0.000000 -1.000000 0.000000 0.000000 0.000000
Notice the second line of the debug output. The frame of the preview layer is moved by 128,-128.
Can anyone explain me why is this happening and how to fix the orientation issues with the preview layer?
thank you,
Janusz
What about this?
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
_videoPreviewLayer.connection.videoOrientation = toInterfaceOrientation;
}
This the method I use in the view controller to maintain the orientation of the capture layer so that it is always right-side-up:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
if (_previewLayer.connection.supportsVideoOrientation)
{
switch (toInterfaceOrientation)
{
case UIInterfaceOrientationPortrait:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortrait;
break;
}
case UIInterfaceOrientationPortraitUpsideDown:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
}
case UIInterfaceOrientationLandscapeLeft:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
break;
}
case UIInterfaceOrientationLandscapeRight:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
break;
}
}
}
A little wordy, but safe even if either enumeration ever changes in the future.
I am still not sure what's causing the problem but I managed to fix it. Here is how I did it:
in viewDidLoad I am adding a layer:
CGRect layerRect = [[[self view] layer] bounds];
[[[self captureManager] previewLayer] setBounds:layerRect];
[[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
[[[self view] layer] addSublayer:[[self captureManager] previewLayer]];
Then I am adding call to the rotateLayer method to didRotate
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[self rotateLayer];
}
and finally the rotateLayer method looks like:
-(void)rotateLayer{
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];
switch (orientation) {
case UIDeviceOrientationLandscapeLeft:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress
break;
case UIDeviceOrientationLandscapeRight:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
break;
case UIDeviceOrientationPortraitUpsideDown:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
break;
default:
stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
[stuckview setBounds:layerRect];
break;
}
[stuckview setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
}
I still don't understand why it works in this way. If anyone can explain it will be great.
#Janusz Chudzynski here is the detailed explanation what rotateLayer method does
This method is created after examine how different orientation affect the previewLayer so creater has checked when orientation is in LandscapeLeft then it should be 270 degrees rotated to make it in correct position for that he has used
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2);
M_PI 3.14159265358979323846264338327950288 // pi = 180 degree
+
M_PI_2 1.57079632679489661923132169163975144 // pi/2 = 90 degree
// Total = 270 degree
so creater has noticed that if I will rotate previewLayer to 270 degrees when its in LandscapeLeft then it will be in correct position just like that he has rotate previewLayer for every rotation possible
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
previewLayer.orientation = toInterfaceOrientation;
}
I'm with #Siegfault, although I also found that when my view loads in landscape orientation on the iPad initially, the orientation is still in correct. To fix, I call that same delegate method in viewDidAppear: with the current interfaceOrientation:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0.0];
}
The answer using willRotateToUserInterfaceOrientation works fine, except that that method has been deprecated. So if you're able to use iOS 9, then here's the way to do it, in Swift:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let newOrientation = UIDevice.currentDevice().orientation
switch newOrientation {
case .LandscapeLeft:
self.capturePreviewLayer?.connection.videoOrientation = .LandscapeLeft
case .LandscapeRight:
self.capturePreviewLayer?.connection.videoOrientation = .LandscapeRight
case .Portrait, .Unknown, .FaceUp, .FaceDown:
self.capturePreviewLayer?.connection.videoOrientation = .Portrait
case .PortraitUpsideDown:
self.capturePreviewLayer?.connection.videoOrientation = .PortraitUpsideDown
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
// layout iOS 8+ animated
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
NSString *timingFunc = nil;
switch ( [context completionCurve] )
{
case UIViewAnimationCurveEaseIn: timingFunc = kCAMediaTimingFunctionEaseIn; break;
case UIViewAnimationCurveEaseInOut: timingFunc = kCAMediaTimingFunctionEaseInEaseOut; break;
case UIViewAnimationCurveEaseOut: timingFunc = kCAMediaTimingFunctionEaseOut; break;
case UIViewAnimationCurveLinear: timingFunc = kCAMediaTimingFunctionLinear; break;
}
[CATransaction begin];
[CATransaction setAnimationDuration:[context transitionDuration]];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:timingFunc]];
[self updatePreviewLayer];
[CATransaction commit];
UIInterfaceOrientation toOrientation = [[UIApplication sharedApplication] statusBarOrientation];
// layout ui if needed
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
- (void)updatePreviewLayer
{
CGAffineTransform transform = CGAffineTransformIdentity;
switch ( UIDevice.currentDevice.orientation )
{
case UIDeviceOrientationLandscapeLeft:
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;
case UIDeviceOrientationLandscapeRight:
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
case UIDeviceOrientationPortraitUpsideDown:
transform = CGAffineTransformRotate(transform, M_PI);
break;
default:
break;
}
preview.affineTransform = transform;
preview.frame = self.view.bounds;
}