iPad iOS7 - 在UIPopoverController中使用UIImagePickerController时,预览图像错误

26

我正在使用UIPopoverController中的UIImagePickerController,它在iOS 6上运行完美。但在iOS 7上,“预览”图片会旋转,但是如果我拍照,照片保存是正确的。

这是我获取picker的方式:

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.mediaTypes = [NSArray arrayWithObjects:
                              (NSString *) kUTTypeImage,
                              nil];
imagePicker.allowsEditing = NO;

并将其添加到弹出控制器:

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];

这些是用于UIScrollView上按钮位置的计算,以便在正确位置显示弹出窗口:

presentPopoverFromRect:CGRectMake(aPosViewA.x, cameraButton_y, 100.0, 30.0)

我认为问题不在那里,因为我已经尝试了几种组合。

我也尝试在全屏模式下拍摄图像,但是应用程序只允许使用横向模式。如果以纵向模式拍摄图像并关闭模态视图,则应用程序也会保持在纵向模式。我找不到一种方法来防止UIImagePickerController切换到纵向模式,或者在模态视图关闭后强制应用程序返回横向模式。

更新

我从这里获取了答案并且更进一步。

在创建选择器并显示弹出窗口之前,我对视图进行了转换:

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;
    }

只要我不转动iPad,这种方法就能正常工作。为此,我正在注册方向改变事件:
[[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(orientationChanged:)  name:UIDeviceOrientationDidChangeNotification  object:nil];

并更改选择器视图:

- (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;
        }
    }
}

剩余问题: 一开始我写过,当照片被拍摄时,它会正确地显示以接受或拒绝它。现在这也被转换了。不知何故,我需要知道图片何时被拍摄并将其转换回来。

而且,这真的是一个令人讨厌的hack,可能在下一个iOS更新中无法正常工作。有人有想法如何以更干净的方式实现它吗?

更新2:

这太恶心了,我找到了一个更干净的解决方案,解决了我的问题,但不是关于在弹出窗口控制器中使用图像选择器的初始问题的答案,这不被苹果推荐,但允许使用。

我现在已经对UIImagePickerController进行了子类化,如下所示:

@implementation QPImagePickerController

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}

- (BOOL)shouldAutorotate {
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscape;
}

@end

我正在使用全屏的图像选择器而非在一个弹出框里。目前已在iOS7上进行测试。


2
我遇到了完全相同的问题。我简直不敢相信这不是一个更相关的问题——它为什么没有破坏更多的应用程序呢? - daveMac
完全相同的问题,这将会在下一个更新中修复吗? - user1838169
据我所见,同样的问题影响了使用弹出式相机的iOS 7 Facebook应用程序。 - Mike
在iOS7中,仅支持iPad横向模式的应用程序出现了“UIApplicationInvalidInterfaceOrientation”错误,原因是“Supported orientations has no common orientation with the application, and shouldAutorotate is returning YES”。 - Peter Lapisu
4个回答

14

UIImagePickerController有一个名为cameraViewTransform的属性。对其应用CGAffineTransform会转换预览图像,但不会转换捕获的图像,因此将正确捕获。我有与您描述的相同问题,并通过以下方式解决了它(针对iOS7):创建我的相机控制器并将其放置在弹出窗口中:

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];

在横屏模式下,我还缩放图像以便使其填充取景器。在我看来,这样做有点讨厌,但我希望能在iOS7.1发布后将其删除。


3

我找到了另一种解决方案,可以处理在显示UIIMagePickerView时设备旋转的情况,基于Journeyman提供的答案。它还可以处理从UIOrientationLandscapeRight/UIOrientationLandscapeLeft旋转回UIOrientationPortrait的情况。

我创建了UIImagePickerController的子类:

#import <UIKit/UIKit.h>

@interface PMImagePickerController : UIImagePickerController

@end

然后将其注册以接收通知,如果设备方向发生变化:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fixCameraOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];

选择器fixCameraOrientation包含Journeyman的代码,里面有一个额外的案例,通过检查sourceType是否为相机进行了包装:
- (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;
        }
    }

}

这里的重要情况是设备方向变为纵向。在这种情况下,需要重置叠加视图的视图。此外,在图像选择器视图加载后调用fixCameraOrientation选择器也很重要,以防设备旋转:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self fixCameraOrientation:nil];
}

2

我的应用程序也遇到了类似的情况。但是在iOS7中,预览正确旋转而在iOS8中不正确。这段代码假设您有多个方向。

第一件事是子类化 UIImagePickerController

从顶部开始,在你的 .m 文件中添加 #import <AVFoundation/AVFoundation.h>

还要添加一个属性来保存初始方向 @property (nonatomic) UIInterfaceOrientation startingOrientation; 和另一个用于删除裁剪条件的属性 @property (nonatomic) BOOL didAttemptToRemoveCropping;

我们将监听几个通知。UIApplicationDidChangeStatusBarOrientationNotification 显然是用于侦听设备旋转的。 AVCaptureSessionDidStartRunningNotification 在相机开始捕获时立即调用。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(captureSessionDidStart:) name:AVCaptureSessionDidStartRunningNotification object:nil];

-captureSessionDidStart: 中,添加一个条件来验证视图实际上是否在屏幕上,并确保相机应该被显示 if (self.view.window && self.sourceType == UIImagePickerControllerSourceTypeCamera)。如果是这样的话,设置起始方向 self.startingOrientation = [UIApplication sharedApplication].statusBarOrientation;
-statusBarOrientationDidChange: 中添加与上面相同的条件,但这次如果为真,则会更新相机变换。首先,我们根据初始旋转获取偏移旋转。这在您以非纵向方式进入UIImagePickerController时是必需的。
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;
}));

最后,我们将尝试消除在起始方向的任何90度方向上出现的黑色条纹。(这可能只是iOS8的问题。)稍微详细一些,如果我以竖屏模式进入UIImagePickerController,然后旋转到横屏模式,预览的顶部和底部会出现黑色条纹。解决方法不是缩放而是去除父视图的裁剪。我们只需要尝试一次,因此首先检查是否已调用此代码。还要确保仅在旋转时调用此代码。如果在初始方向中调用它,它将无法立即正常工作。

if (!self.didAttemptToRemoveCropping && self.startingOrientation != [UIApplication sharedApplication].statusBarOrientation) {
    self.didAttemptToRemoveCropping = YES;

    [self findClippedSubviewInView:self.view];
}

最后,在-findClippedSubviewInView:的代码中,我们循环遍历所有子视图,以搜索具有.clipsToBounds = YES的视图。 如果这是真的,我们将再添加一个条件来验证其祖先超级视图之一是否正确。

for (UIView* subview in view.subviews) {
    if (subview.clipsToBounds) {
        if ([self hasAncestorCameraView:subview]) {
            subview.clipsToBounds = NO;
            break;
        }
    }

    [self findClippedSubviewInView:subview];
}

-hasAncestorCameraView:方法中,我们只需沿着superview链循环,并在类名中有CameraView时返回true。
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];
}

这是代码的分解,这里将它们合在一起。
#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

几乎完美地工作,但它看起来有些奇怪,似乎在进行比必要更多的旋转。 - TMob

1
iPad带有相机 - 不要在弹出窗口中显示。相反,像在iPhone上一样,使用模态视图控制器呈现。(至少从iOS 7开始)

正是我所需要的。在iOS 8 iPad横屏模式下,弹出视图对于库而言是最好的选择,但对于相机来说模态视图呈现更好。 - Codezy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接