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