style alertas ios cocoa-touch ios8 uialertview uialertcontroller

ios - alertas - UIAlertController se mueve a la posición con errores en la parte superior de la pantalla cuando llama a `presentViewController:`



uialertcontroller styles (8)

Además de la respuesta de Carl Lindberg, hay dos casos que también deben tenerse en cuenta:

  1. Dispositivo giratorio
  2. Altura del teclado cuando hay un campo de texto dentro de alerta

Entonces, la respuesta completa que funcionó para mí:

// fix for rotation -(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) { } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { [self.view setNeedsLayout]; }]; } // fix for keyboard - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)keyboardWillShow:(NSNotification *)notification { NSDictionary *keyboardUserInfo = [notification userInfo]; CGSize keyboardSize = [[keyboardUserInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; self.keyboardHeight = keyboardSize.height; [self.view setNeedsLayout]; } - (void)keyboardWillHide:(NSNotification *)notification { self.keyboardHeight = 0; [self.view setNeedsLayout]; } // position layout fix -(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self fixAlertPosition]; } -(void)fixAlertPosition { if (self.preferredStyle == UIAlertControllerStyleAlert && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { CGRect myFrame = self.view.frame; CGRect superBounds = self.view.superview.bounds; myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2; myFrame.origin.y = (superBounds.size.height - myFrame.size.height - self.keyboardHeight) / 2; self.view.frame = myFrame; } } else if (self.preferredStyle == UIAlertControllerStyleActionSheet && self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { UIScreen *screen = self.view.window.screen; CGFloat borderPadding = ((screen.nativeBounds.size.width / screen.nativeScale) - myRect.size.width) / 2.0f; CGRect myFrame = self.view.frame; CGRect superBounds = self.view.superview.bounds; myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2; myFrame.origin.y = superBounds.size.height - myFrame.size.height - borderPadding; self.view.frame = myFrame; } } }

Además, si usa la categoría, entonces necesita almacenar la altura del teclado de alguna manera, como esto:

@interface UIAlertController (Extended) @property (nonatomic) CGFloat keyboardHeight; @end @implementation UIAlertController (Extended) static char keyKeyboardHeight; - (void) setKeyboardHeight:(CGFloat)height { objc_setAssociatedObject (self,&keyKeyboardHeight,@(height),OBJC_ASSOCIATION_RETAIN); } -(CGFloat)keyboardHeight { NSNumber *value = (id)objc_getAssociatedObject(self, &keyKeyboardHeight); return value.floatValue; } @end

La presentación de una vista desde un UIAlertController mueve la alerta a una posición con errores en la esquina superior izquierda de la pantalla. iOS 8.1, dispositivo y simulador.

Hemos notado esto en una aplicación cuando intentamos presentar una vista desde la vista actual "máxima". Si un UIAlertController pasa a ser la vista superior, obtenemos este comportamiento. Hemos cambiado nuestro código para simplemente ignorar UIAlertControllers, pero estoy publicando esto en caso de que otros tengan el mismo problema (ya que no pude encontrar nada).

Hemos aislado esto en un proyecto de prueba simple, código completo en la parte inferior de esta pregunta.

  1. Implemente viewDidAppear: en View Controller en un nuevo proyecto Xcode de vista única.
  2. Presente una alerta UIAlertController .
  3. El controlador de alerta llama inmediatamente a presentViewController:animated:completion: para mostrar y luego descartar otro controlador de vista:

En el momento en que comienza la animación presentViewController:... , el UIAlertController se mueve a la esquina superior izquierda de la pantalla:

Cuando dismissViewControllerAnimated: la dismissViewControllerAnimated: animación, la alerta se ha movido aún más en el margen superior izquierdo de la pantalla:

Código completo:

- (void)viewDidAppear:(BOOL)animated { // Display a UIAlertController alert NSString *message = @"This UIAlertController will be moved to the top of the screen if it calls `presentViewController:`"; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"UIAlertController iOS 8.1" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"I think that''s a Bug" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; // The UIAlertController should Present and then Dismiss a view UIViewController *viewController = [[UIViewController alloc] init]; viewController.view.backgroundColor = self.view.tintColor; [alert presentViewController:viewController animated:YES completion:^{ dispatch_after(0, dispatch_get_main_queue(), ^{ [viewController dismissViewControllerAnimated:YES completion:nil]; }); }]; // RESULT: // UIAlertController has been moved to the top of the screen. // http://i.imgur.com/KtZobuK.png }

¿Hay algo en el código anterior que podría estar causando este problema? ¿Existe alguna alternativa que permita la presentación sin errores de una vista desde un UIAlertController?

rdar: // 19037589
http://openradar.appspot.com/19037589


Creo que solo deberías categorizar UIAlertController de esta manera:

@implementation UIAlertController(UIAlertControllerExtended) - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (self.preferredStyle == UIAlertControllerStyleAlert) { __weak UIAlertController *pSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ CGRect screenRect = [[UIScreen mainScreen] bounds]; CGFloat screenWidth = screenRect.size.width; CGFloat screenHeight = screenRect.size.height; [pSelf.view setCenter:CGPointMake(screenWidth / 2.0, screenHeight / 2.0)]; [pSelf.view setNeedsDisplay]; }); } } @end


El usuario manoj.agg publicó esta respuesta en el informe de error de Open Radar , pero dice:

De alguna manera, no tengo la reputación suficiente para publicar respuestas en .

Publicando su respuesta aquí para la posteridad. No lo he probado / evaluado.

Paso 1:

Cree un controlador de vista personalizado heredado de UIViewController e implemente UIPopoverPresentationControllerDelegate :

@interface CustomUIViewController : UIViewController<UITextFieldDelegate, UIPopoverPresentationControllerDelegate>

Paso 2:

Presente la vista en pantalla completa, haciendo uso de la presentación emergente:

CustomUIViewController *viewController = [[CustomUIViewController alloc] init]; viewController.view.backgroundColor = self.view.tintColor; viewController.modalPresentationStyle = UIModalPresentationOverFullScreen; UIPopoverPresentationController *popController = viewController.popoverPresentationController; popController.delegate = viewController; [alert presentViewController:viewController animated:YES completion:^{ dispatch_after(0, dispatch_get_main_queue(), ^{ [viewController dismissViewControllerAnimated:YES completion:nil]; }); }];

Tuve un problema similar cuando se necesitaba mostrar una vista de entrada de contraseña encima de cualquier otro controlador de vista, incluidos los controles UIAlertControllers. El código anterior me ayudó a resolver el problema. Un cambio notable en iOS 8 es que UIAlertController hereda de UIViewController , que no fue el caso de UIAlertView .


Encontré una situación donde a veces una vista modal se presentaba encima de una alerta (situación tonta, lo sé), y el control UIAlertController podía aparecer en la esquina superior izquierda (como la segunda captura de pantalla de la pregunta original ), y encontré una Solución de una sola línea que parece funcionar. Para el controlador que está a punto de presentarse en el UIAlertController, cambie su estilo de presentación modal así:

viewControllerToBePresented.modalPresentationStyle = .OverFullScreen

Esto debe hacerse justo antes de llamar a presentViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion completion: (() -> Void)?)


Eso es un poco decepcionante ... mover las alertas para que sean UIViewControllers, pero luego no permitir el uso regular de ellas. Trabajo en una aplicación que hizo algo similar, a veces tiene que saltar a un nuevo contexto de usuario, y al hacerlo presentaba un nuevo controlador de vista encima de lo que estaba allí. En realidad, tener las alertas para ser controladores de vista es casi mejor en este caso, ya que se conservarán. Pero estamos viendo el mismo desplazamiento ahora que hemos cambiado a UIViewControllers.

Es posible que tengamos que encontrar una solución diferente (utilizando ventanas diferentes, tal vez), y tal vez evitemos la presentación si el nivel superior es un controlador UIAlert. Pero, es posible restablecer el posicionamiento correcto. Puede que no sea una buena idea, porque el código podría romperse si Apple cambia su posición en la pantalla, pero la siguiente subclase parece funcionar (en iOS8) si esta funcionalidad es muy necesaria.

@interface MyAlertController : UIAlertController @end @implementation MyAlertController /* * UIAlertControllers (of alert type, and action sheet type on iPhones/iPods) get placed in crazy * locations when you present a view controller over them. This attempts to restore their original placement. */ - (void)_my_fixupLayout { if (self.preferredStyle == UIAlertControllerStyleAlert && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { CGPoint center = self.view.window.center; CGPoint myCenter = [self.view.superview convertPoint:center fromView:nil]; self.view.center = myCenter; } } else if (self.preferredStyle == UIAlertControllerStyleActionSheet && self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone && self.view.window) { CGRect myRect = self.view.bounds; CGRect windowRect = [self.view convertRect:myRect toView:nil]; if (!CGRectContainsRect(self.view.window.bounds, windowRect) || CGPointEqualToPoint(windowRect.origin, CGPointZero)) { UIScreen *screen = self.view.window.screen; CGFloat borderPadding = ((screen.nativeBounds.size.width / screen.nativeScale) - myRect.size.width) / 2.0f; CGRect myFrame = self.view.frame; CGRect superBounds = self.view.superview.bounds; myFrame.origin.x = CGRectGetMidX(superBounds) - myFrame.size.width / 2; myFrame.origin.y = superBounds.size.height - myFrame.size.height - borderPadding; self.view.frame = myFrame; } } } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self _my_fixupLayout]; } @end

Apple puede considerar que el posicionamiento de la vista es privado, por lo que restaurarlo de esta manera puede no ser la mejor idea, pero funciona por ahora. Podría ser posible almacenar fuera del marco anterior en una anulación de -presentViewController: animated :, y simplemente restaurarlo en lugar de volver a calcularlo.

Es posible cambiar el UIAlertController para hacer el equivalente al anterior, que también cubriría UIAlertControllers presentado por código que no controlas, pero prefiero usar swizzles en lugares donde es un error que Apple solucionará (por lo tanto, es un momento en el que se puede eliminar el swizzle, y permitimos que el código existente "funcione" sin eliminarlo solo por una solución de error). Pero si es algo que Apple no va a arreglar (indicado por su respuesta como se señala en otra respuesta aquí), entonces generalmente es más seguro tener una clase separada para modificar el comportamiento, por lo que solo lo está usando en circunstancias conocidas.


Yo también estaba teniendo este problema. Si presenté un controlador de vista mientras se presentaba un UIAlertController, la alerta iría a la parte superior izquierda.

Mi solución es actualizar el centro de la vista de UIAlertController en viewDidLayoutSubviews; Logrado subclasificando UIAlertController.

class MyBetterAlertController : UIAlertController { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let screenBounds = UIScreen.mainScreen().bounds if (preferredStyle == .ActionSheet) { self.view.center = CGPointMake(screenBounds.size.width*0.5, screenBounds.size.height - (self.view.frame.size.height*0.5) - 8) } else { self.view.center = CGPointMake(screenBounds.size.width*0.5, screenBounds.size.height*0.5) } } }


http://openradar.appspot.com/19037589 fue cerrado por Apple

Relaciones con desarrolladores de Apple | 25-feb-2015 10:52 a.m.

No hay planes para abordar esto en base a lo siguiente:

Esto no es compatible, por favor evite presentar en un UIAlertController.

Estamos cerrando este informe.

Si tiene preguntas sobre la resolución, o si esto sigue siendo un problema crítico para usted, entonces actualice su informe de errores con esa información.

Por favor, asegúrese de revisar periódicamente los nuevos lanzamientos de Apple para cualquier actualización que pueda afectar este problema.


var kbHeight: CGFloat = 0 override func keyboardWillShow(_ notification: Notification) { if let userInfo = notification.userInfo { if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { kbHeight = keyboardSize.height self.animateTextField(up: true) } } } override func keyboardWillHide(_ notification: Notification) { self.animateTextField(up: false) } func animateTextField(up: Bool) { let movement = (up ? -kbHeight : kbHeight) UIView.animate(withDuration: 0.3, animations: { self.view.frame = CGRect.offsetBy(self.view.frame)(dx: 0, dy: movement) }) }