UIImagePickerController camera preview is portrait in landscape app

Viewed 39015

In my landscape-only iPhone application, I launch a UIImagePickerController to take a photo, but the live image displayed from the camera is in portrait orientation, with blank space around it. The image is rotated.

Once the camera button is pressed, the preview is very messy, with most of the preview off screen, and views not correctly aligned.

Apple has acknowledged that this is defect, and is working on it.

My question is, does anyone have a work-around (legal or illegal) that would allow me to get this working now. I wouldn't release to the App Store with an illegal fix, but I would have a much better app for user testing - currently the camera is pretty much unusable in landscape.

I will attach a simple test project and images if I can.

Edit - just to clarify, the image I get is correctly landscape. I want the camera & preview UIs to look right!


Camera

alt text

7 Answers

The answer is more ridiculous than you might think. I had the same problem and found a solution in a forum somewhere. Pass your taken image into a method like this:

// Code from: http://discussions.apple.com/thread.jspa?messageID=7949889
- (UIImage *)scaleAndRotateImage:(UIImage *)image {
  int kMaxResolution = 640; // Or whatever

    CGImageRef imgRef = image.CGImage;

    CGFloat width = CGImageGetWidth(imgRef);
    CGFloat height = CGImageGetHeight(imgRef);


    CGAffineTransform transform = CGAffineTransformIdentity;
    CGRect bounds = CGRectMake(0, 0, width, height);
    if (width > kMaxResolution || height > kMaxResolution) {
        CGFloat ratio = width/height;
        if (ratio > 1) {
            bounds.size.width = kMaxResolution;
            bounds.size.height = roundf(bounds.size.width / ratio);
        }
        else {
            bounds.size.height = kMaxResolution;
            bounds.size.width = roundf(bounds.size.height * ratio);
        }
    }

    CGFloat scaleRatio = bounds.size.width / width;
    CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
    CGFloat boundHeight;
    UIImageOrientation orient = image.imageOrientation;
    switch(orient) {

        case UIImageOrientationUp: //EXIF = 1
            transform = CGAffineTransformIdentity;
            break;

        case UIImageOrientationUpMirrored: //EXIF = 2
            transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            break;

        case UIImageOrientationDown: //EXIF = 3
            transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationDownMirrored: //EXIF = 4
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
            transform = CGAffineTransformScale(transform, 1.0, -1.0);
            break;

        case UIImageOrientationLeftMirrored: //EXIF = 5
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationLeft: //EXIF = 6
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationRightMirrored: //EXIF = 7
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeScale(-1.0, 1.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        case UIImageOrientationRight: //EXIF = 8
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        default:
            [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];

    }

    UIGraphicsBeginImageContext(bounds.size);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
        CGContextScaleCTM(context, -scaleRatio, scaleRatio);
        CGContextTranslateCTM(context, -height, 0);
    }
    else {
        CGContextScaleCTM(context, scaleRatio, -scaleRatio);
        CGContextTranslateCTM(context, 0, -height);
    }

    CGContextConcatCTM(context, transform);

    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
    UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return imageCopy;
}

:(

Let me know if this is discouraged re-posting the best answer above, I have a 2019 swift version for Alex Wayne's answer.

/**
 Scale and rotate a UIImage so that it is correctly oriented

 :param: image: The image to be rotated

 :returns: UIImage
 */
func scaleAndRotateImage(_ image: UIImage) -> UIImage {
    let kMaxResolution: CGFloat = 640
    let imgRef: CGImage = image.cgImage!
    let width: CGFloat = CGFloat(imgRef.width)
    let height: CGFloat = CGFloat(imgRef.height)
    var transform: CGAffineTransform = CGAffineTransform.identity
    var bounds: CGRect = CGRect(x: 0, y: 0, width: width, height: height)
    if width > kMaxResolution || height > kMaxResolution {
        let ratio: CGFloat = width / height
        if ratio > 1 {
            bounds.size.width = kMaxResolution
            bounds.size.height = (bounds.size.width / ratio)
        }
        else {
            bounds.size.height = kMaxResolution
            bounds.size.width = (bounds.size.height * ratio)
        }
    }
    let scaleRatio: CGFloat = bounds.size.width / width
    let imageSize: CGSize = CGSize(width: CGFloat(imgRef.width), height: CGFloat(imgRef.height))
    var boundHeight: CGFloat
    let orient: UIImage.Orientation = image.imageOrientation
    switch orient {
    case UIImageOrientation.up:
        transform = CGAffineTransform.identity
    case UIImageOrientation.upMirrored:
        transform = CGAffineTransform(translationX: imageSize.width, y: 0.0)
        transform = transform.scaledBy(x: -1.0, y: 1.0)
    case UIImageOrientation.down:
        transform = CGAffineTransform(translationX: imageSize.width, y: imageSize.height)
        transform = transform.rotated(by: CGFloat(Double.pi))
    case UIImageOrientation.downMirrored:
        transform = CGAffineTransform(translationX: 0.0, y: imageSize.height)
        transform = transform.scaledBy(x: 1.0, y: -1.0)
    case UIImageOrientation.leftMirrored:
        boundHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = boundHeight
        transform = CGAffineTransform(translationX: imageSize.height, y: imageSize.width)
        transform = transform.scaledBy(x: -1.0, y: 1.0)
        transform = transform.rotated(by: CGFloat(3.0 * .pi / 2.0))
    case UIImageOrientation.left:
        boundHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = boundHeight
        transform = CGAffineTransform(translationX: 0.0, y: imageSize.width)
        transform = transform.rotated(by: CGFloat(3.0 * .pi / 2.0))
    case UIImageOrientation.rightMirrored:
        boundHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = boundHeight
        transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
        transform = transform.rotated(by: CGFloat(.pi / 2.0))
    case UIImageOrientation.right:
        boundHeight = bounds.size.height
        bounds.size.height = bounds.size.width
        bounds.size.width = boundHeight
        transform = CGAffineTransform(translationX: imageSize.height, y: 0.0)
        transform = transform.rotated(by: CGFloat(.pi / 2.0))
    }
    UIGraphicsBeginImageContext(bounds.size)
    let context: CGContext = UIGraphicsGetCurrentContext()!
    if orient == UIImage.Orientation.right || orient == UIImage.Orientation.left {
        context.scaleBy(x: -scaleRatio, y: scaleRatio)
        context.translateBy(x: -height, y: 0)
    }
    else {
        context.scaleBy(x: scaleRatio, y: -scaleRatio)
        context.translateBy(x: 0, y: -height)
    }
    context.concatenate(transform)
    UIGraphicsGetCurrentContext()?.draw(imgRef, in: CGRect(x: 0, y: 0, width: width, height: height))
    let imageCopy: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return imageCopy
}

This issue can be resolved by setting modalPresentationStyle to overCurrentContext as below,

 picker.modalPresentationStyle = .overCurrentContext

and presenting your image picker on Main Thread like,

DispatchQueue.main.async {

   self.present(picker, animated: true) {

   }
}

Why this is happening?

Because ImagePickerController only works in portrait mode and when you trying to present picker from landscape viewcontroller it's try to set your statusbar in portrait mode. So, If you set modalPresentationStyle to overCurrentContext then it will not try to set orientation. It will consider current orientation.

see the Apple guide line for ImagePickerController. It states,

The UIImagePickerController class supports portrait mode only. This class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified, with one exception. You can assign a custom view to the cameraOverlayView property and use that view to present additional information or manage the interactions between the camera interface and your code.

Related