web-dev-qa-db-ja.com

iPadiOS7-UIPopoverControllerのUIImagePickerControllerのプレビュー画像が間違っています

IOS6で完全に機能するUIPopoverController内でUIImagePickerControllerを使用しています。 iOS 7では、画像をキャプチャするために表示される「プレビュー」画像は回転しますが、写真を撮ると正しく保存されます。

これが私のピッカーを取得する方法です:

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

残りの問題:最初に書いたように、写真が撮られたとき、それを受け入れるか却下するかが正しく示されました。これも変換されます。どういうわけか私は画像がいつ撮られたかを知り、それを元に戻す必要があります。

そして、これは本当に厄介なハックであり、おそらく次のiOSアップデートでは機能しません。誰かがそれをよりクリーンな方法で実装する方法を考えていますか?

更新2

これはあまりにも厄介でした。私の問題を解決するよりクリーンな解決策を見つけましたが、Appleによって推奨されていないが許可されているポップオーバーコントローラーのイメージピッカーに関する最初の質問に対する答えではありません。

UIImagePickerControllerを次のようにサブクラス化しました。

@implementation QPImagePickerController

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

- (BOOL)shouldAutorotate {
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscape;
}

@end

ポップオーバーではなく、フルスクリーンでイメージピッカーを使用しています。 iOS7でこれまでにテストされています。

26
Marcus Franzen

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が到着したら削除できるといいのですが。

14
Journeyman

Journeymanが提供した回答に基づいて、UIIMagePickerViewが表示されているときにデバイスが回転する場合を処理する別のソリューションを見つけました。また、ビューがUIOrientationLandscapeRight/UIOrientationLandscapeLeftからUIOrientationPortraitに回転して戻る場合も処理します。

UIImagePickerControllerのサブクラスを作成しました。

#import <UIKit/UIKit.h>

@interface PMImagePickerController : UIImagePickerController

@end

次に、デバイスの向きが変わった場合に通知を受け取るように登録しました。

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

セレクターfixCameraOrientationには、sourceTypeがカメラであることを確認するためのチェックでラップされた1つの余分なケースを含むJourneymanのコードが含まれています。

- (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];
}
3
PeterMalmgren

私のアプリでも同様の状況がありました。ただし、プレビューはiOS8ではなくiOS7で正しく回転していました。このコードは、複数の方向があることを前提としています。

まず、UIImagePickerControllerをサブクラス化します。

上から始めて、_#import <AVFoundation/AVFoundation.h>_を.mファイルに追加します。

また、初期方向@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:_に上記と同じ条件を追加しますが、今回はtrueの場合、カメラ変換を更新します。まず、最初の回転に基づいてオフセット回転を取得します。これは、縦向き以外の向きで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に入ってから横向きに回転すると、プレビューの上下に黒いバーが表示されます。これに対する解決策は、スケーリングではなく、スーパービューのクリッピングを削除することです。この試行は1回だけ行う必要があるため、最初にこのコードを呼び出したかどうかを確認してください。また、ローテーションした場合にのみこのコードを呼び出すようにしてください。最初の向きで呼び出された場合、すぐには機能しません。

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

    [self findClippedSubviewInView:self.view];
}
_

最後に、_-findClippedSubviewInView:_のコードで、すべてのサブビューをループして、_.clipsToBounds = YES_のビューを検索します。それが本当なら、その祖先のスーパービューの1つが正しいことを確認するためにもう1つの条件を作成します。

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

    [self findClippedSubviewInView:subview];
}
_

_-hasAncestorCameraView:_では、スーパービューチェーンをループアップし、クラスの1つに名前に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
_
2
cnotethegr8

カメラ付きiPad-ポップオーバーに表示しないでください。代わりに、iPhoneの場合と同様に、モーダルビューコントローラーに表示されます。 (少なくともiOS 7以降)

1
Ernie Thomason