iPad iOS7: UIImagePickerController en UIPopoverController tiene una imagen de vista previa incorrecta
uiinterfaceorientation (4)
Estoy usando un UIImagePickerController dentro de un UIPopoverController que funciona perfectamente con iOS6. Con iOS 7, la imagen de "vista previa" que se muestra para capturar la imagen se rota, pero si tomo una imagen, se guarda correctamente.
Así es como obtengo mi selector:
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.mediaTypes = [NSArray arrayWithObjects:
(NSString *) kUTTypeImage,
nil];
imagePicker.allowsEditing = NO;
Y agregarlo a un controlador de popover:
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];
Esos son cálculos para la posición del botón en un UIScrollView para mostrar la ventana emergente en la posición correcta:
presentPopoverFromRect:CGRectMake(aPosViewA.x, cameraButton_y, 100.0, 30.0)
No creo que el problema esté ahí, ya que he probado varias combinaciones.
También he intentado capturar la imagen en modo de pantalla completa, pero la aplicación solo puede usar el modo horizontal. Si la imagen se toma en modo retrato y se despide la vista modal, la aplicación también permanece en modo retrato. No pude encontrar una manera de evitar que el UIImagePickerController cambie al modo vertical o que la aplicación vuelva al modo horizontal si la vista modal fue descartada.
ACTUALIZAR
He tomado la respuesta de here y he ido un paso más allá.
Transformo la vista después de crear el selector y antes de mostrar la ventana emergente:
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;
}
que funciona siempre y cuando no gire el iPad. Para eso me registro para el evento de orientación cambiado:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
y cambiar la vista del selector:
- (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;
}
}
}
PROBLEMA RESTANTE: Como escribí al principio, cuando se tomó la foto, se mostró correctamente para aceptarla o descartarla. Esto ahora también se transforma. De alguna manera necesito saber cuándo se toma la imagen y transformarla de nuevo.
Y, esto es realmente un truco desagradable y probablemente no funcione con la próxima actualización de iOS. ¿Alguien tiene una idea de cómo implementar eso de una manera más limpia?
ACTUALIZACIÓN 2
Esto fue demasiado desagradable, he encontrado una solución más limpia que resuelve mi problema pero no es la respuesta a la pregunta inicial con respecto a un selector de imágenes en un controlador emergente, que Apple no recomienda, pero está permitido.
He subclasificado ahora el UIImagePickerController como este:
@implementation QPImagePickerController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscape;
}
@end
y estoy usando el selector de imagen en pantalla completa en lugar de una ventana emergente. Probado hasta ahora en iOS7.
El UIImagePickerController tiene una propiedad llamada cameraViewTransform. La aplicación de un CGAffineTransform a esto transformará la imagen de vista previa pero no transformará la imagen capturada que, por lo tanto, se capturará correctamente. Tengo el mismo problema que describe y lo resolví (para iOS7) creando mi controlador de cámara y colocándolo en una ventana emergente de la siguiente manera:
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];
También escala la imagen cuando está en paisaje para que llene el visor más de lo que lo haría de otra manera. En mi opinión, todo esto es bastante desagradable, pero espero poder eliminarlo una vez que llegue iOS7.1.
He encontrado otra solución que maneja el caso en el que un dispositivo se rota mientras el UIIMagePickerView se presenta según la respuesta que proporcionó Journeyman. También maneja el caso en el que la vista se rota de UIOrientationLandscapeRight / UIOrientationLandscapeLeft de nuevo a UIOrientationPortrait.
Creé una subclase de UIImagePickerController:
#import <UIKit/UIKit.h>
@interface PMImagePickerController : UIImagePickerController
@end
Luego lo registró para recibir notificaciones si cambia la orientación del dispositivo:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fixCameraOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
El selector fixCameraOrientation contiene el código de Journeyman con un estuche adicional, envuelto en un cheque para asegurarse de que el tipo de fuente es la cámara:
- (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;
}
}
}
El caso importante aquí es el caso en el que la orientación del dispositivo va a retrato. La vista de la superposición debe restablecerse en este caso. También es importante que se llame al selector fixCameraOrientation después de que se cargue la vista del selector de imágenes, en caso de que se gire el dispositivo:
- (void)viewDidLoad
{
[super viewDidLoad];
[self fixCameraOrientation:nil];
}
Tuve una situación similar en mi aplicación. Sin embargo, la vista previa estaba rotando correctamente en iOS7 y no en iOS8. Este código asume que tienes más de una orientación.
Lo primero es subclase UIImagePickerController
.
A partir de la parte superior, agregue #import <AVFoundation/AVFoundation.h>
a su archivo .m.
También agregue una propiedad para guardar la orientación inicial @property (nonatomic) UIInterfaceOrientation startingOrientation;
y otro para una condición para eliminar clipping @property (nonatomic) BOOL didAttemptToRemoveCropping;
.
Vamos a escuchar un par de notificaciones. UIApplicationDidChangeStatusBarOrientationNotification
es obviamente escuchar la rotación del dispositivo. AVCaptureSessionDidStartRunningNotification
se llama justo cuando la cámara comienza a capturar.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(captureSessionDidStart:) name:AVCaptureSessionDidStartRunningNotification object:nil];
En -captureSessionDidStart:
agregue una condición para verificar que la vista está realmente en la pantalla y para asegurarse de que la cámara se muestre if (self.view.window && self.sourceType == UIImagePickerControllerSourceTypeCamera)
. Si es así, establezca la orientación inicial self.startingOrientation = [UIApplication sharedApplication].statusBarOrientation;
.
En -statusBarOrientationDidChange:
agregue la misma condición que la anterior, pero esta vez si es cierto, actualizaremos la transformación de la cámara. Primero obtenemos la rotación offset basada en la rotación inicial. Esto es necesario cuando ingresa el UIImagePickerController
en orientaciones distintas a la vertical.
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;
});
A continuación actualizaremos la transformada de la cámara con la rotación actual.
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;
}));
Y, finalmente, intentaremos eliminar las barras negras presentadas en una orientación de 90 grados desde la orientación inicial. (Esto podría ser solo un problema de iOS8). Con un poco más de detalle, si ingreso el UIImagePickerController
en modo vertical y luego gire a horizontal, habrá barras negras en la parte superior e inferior de la vista previa. La solución para esto no es escalar, sino eliminar el recorte de una supervisión. Solo necesitamos realizar este intento una vez, así que primero compruebe si hemos llamado a este código. También asegúrese de que solo llamemos a este código si hemos girado. Si se llama en la orientación inicial, no funcionará de inmediato.
if (!self.didAttemptToRemoveCropping && self.startingOrientation != [UIApplication sharedApplication].statusBarOrientation) {
self.didAttemptToRemoveCropping = YES;
[self findClippedSubviewInView:self.view];
}
Finalmente, en el código para -findClippedSubviewInView:
recorremos todas las subvistas para buscar una vista con .clipsToBounds = YES
. Si eso es cierto, hacemos una condición más para verificar que una de sus superviews ancestrales es correcta.
for (UIView* subview in view.subviews) {
if (subview.clipsToBounds) {
if ([self hasAncestorCameraView:subview]) {
subview.clipsToBounds = NO;
break;
}
}
[self findClippedSubviewInView:subview];
}
En -hasAncestorCameraView:
simplemente conectamos la cadena de supervisión y devolvemos verdadero si una de las clases tiene CameraView
en el nombre.
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];
}
Ese es el desglose del código, aquí está todo junto.
#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 con cámara: no se muestra en una ventana emergente. En su lugar, presente en un controlador de vista modal, como lo haría en el iPhone. (al menos comenzando con iOS 7)