IPhone SDK despedir modal ViewControllers en iPad haciendo clic fuera de ella
-
26-09-2019 - |
Pregunta
Quiero despedir a un FormSheetPresentation controlador de vista modal cuando los grifos de los usuarios fuera de la vista modal ... He visto un montón de aplicaciones haciendo esto (ebay en el iPad, por ejemplo), pero no puedo imaginar cómo desde los puntos de vista son debajo discapacitados de toques cuando las opiniones modales aparecen de este modo (¿son presentándolo como un popover tal vez?) ... alguien tiene alguna sugerencia?
Solución
Soy un año de retraso, pero esto es bastante sencillo de hacer.
Haga que su controlador de vista modal adjuntar un reconocedor gesto a la ventana de la vista:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
[recognizer release];
El código de control:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
Eso es todo. HIG condenado, este es un comportamiento útil y, a menudo intuitiva.
Otros consejos
Las otras aplicaciones no están utilizando modal Vistas si permiten que la vista que se desestimó haciendo clic fuera de ella. UIModalPresentationFormSheets
no se puede descartar de esta manera. (Ni, de hecho puede cualquier UIModal en SDK3.2). Sólo el UIPopoverController
puede ser despedido haciendo clic fuera del área. Es muy posible (aunque contra HIG iPad de Apple) para el desarrollador de la aplicación que ha sombreado el fondo de la pantalla y luego se muestra el UIPopoverController
para que se vea como un UIModalPresentationFormSheets
(u otro Ver UIModal).
[...] estilo UIModalPresentationCurrentContext permite a un controlador de vista adopta el estilo de presentación de su matriz. En cada punto de vista modal, las áreas atenuadas muestran el contenido subyacente, pero no permiten grifos en ese contenido. Por lo tanto, a diferencia de un popover, sus puntos de vista modales deben todavía tiene controles que permiten al usuario para que desaparezca el punto de vista modal.
Vea la iPadProgrammingGuide en el sitio de desarrolladores para obtener más información (Página 46 - "Configuración del estilo de presentación de modales Vistas")
Para iOS 8, se debe implementar tanto el UIGestureRecognizer
y cambie el (x, y) las coordenadas del lugar golpeado ligeramente cuando está en orientación horizontal. No estoy seguro si esto es debido a un error de iOS 8.
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
El código anterior funciona bien, pero me gustaría cambiar la sentencia if para,
if (!([self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil] || [self.navigationController.view pointInside:[self.navigationController.view convertPoint:location fromView:self.navigationController.view.window] withEvent:nil]))
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
Esto se asegura de que todavía puede interactuar con la barra de navegación, de lo contrario tocando en ella rechaza el punto de vista modal.
Respuesta actualizado para iOS 8
Al parecer, en iOS 8, el UIDimmingView
tiene un reconocedor del grifo gesto, que interfiere con la implantación inicial, por lo que ignoramos y no requieren que falle.
Esta es la era de la velocidad, por lo que la mayoría son probablemente sólo copiar el código anterior .. Pero, sufro de TOC cuando se trata de código, por desgracia.
Aquí es una solución modular que utiliza la respuesta de Danilo Campos con categorías . También resuelve un fallo importante que puede ocurrir si está rechaza su modal a través de otros medios, como se ha mencionado .
NOTA:. El caso de las declaraciones están ahí porque yo uso el controlador de vista tanto para iPhone y iPad, y sólo el iPad necesita registrar / anular el registro
ACTUALIZACIÓN: Lo esencial ha sido actualizado, ya que no funcionaba correctamente con el impresionante código FCOverlay , y no permitían que los gestos sean reconocidos en la vista presentada. Esos problemas se corrigen. El uso de la categoría es tan fácil como:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.presentingViewController) {
[self registerForDismissOnTapOutside];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
if (self.presentingViewController) {
[self unregisterForDismissOnTapOutside];
}
[super viewWillDisappear:animated];
}
copiar y pegar este código en tu ModalViewController:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Code for dissmissing this viewController by clicking outside it
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
Very important:
Si tiene cualquier otra manera de cerrar el ventana emergente modal , no se olvide de quitar el reconocedor del grifo gesto!
Me olvidó esto, y se puso accidentes locos más adelante, ya que el reconocedor del grifo todavía estaba disparando eventos.
De acuerdo con iOS de Apple HIG, 1. la vista modal no tiene esa capacidad de ser despedido sin ninguna entrada sobre sí mismo; 2. utilizar la vista modal en la situación que se requiere una entrada de usuario.
Uso UIPresentationController lugar:
- (void)presentationTransitionWillBegin
{
[super presentationTransitionWillBegin];
UITapGestureRecognizer *dismissGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(dismissGestureTapped:)];
[self.containerView addGestureRecognizer:dismissGesture];
[[[self presentedViewController] transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:nil];
}
- (void) dismissGestureTapped:(UITapGestureRecognizer *)sender{
if (sender.state==UIGestureRecognizerStateEnded&&!CGRectContainsPoint([self frameOfPresentedViewInContainerView], [sender locationInView:sender.view])) {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
Modificado de ejemplo Lookinside
Esto funciona para mí para iOS7 un 8 y la barra de navegación.
Si usted no necesita la barra de navegación simplemente eliminar location2 y segunda condición en la sentencia if después de las tuberías.
@MiQUEL esto debería funcionar para usted también
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location1 = [sender locationInView:self.view];
CGPoint location2 = [sender locationInView:self.navigationController.view];
if (!([self.view pointInside:location1 withEvent:nil] || [self.navigationController.view pointInside:location2 withEvent:nil])) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Edit: También puede tener que ser un delegado gesto reconocedor para esta y otras soluciones anteriores a trabajar. Hacerlo de esta manera:
@interface CommentTableViewController () <UIGestureRecognizerDelegate>
establecido a sí mismo como el delegado para el reconocedor:
self.recognizer.delegate = self;
y poner en práctica este método delegado:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Es bastante factible.
Tome un vistazo aquí
https://stackoverflow.com/a/26016458/4074557
Es un NavigationController (modal) que despedir automático para iPad (cuando se toca el exterior)
Utilice su interior viewcontroller de la misma.
Espero que ayuda.
Sé que es tarde, pero considerar el uso de CleanModal (probado con iOS 7 y 8).
Puede utilizar MZFormSheetController como esto:
MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];
En Swift 2 / Xcode Versión 7.2 (7C68) el siguiente código que funcionó para mí.
Atención: este código debe ser puesto en el archivo de la hoja de ViewController.swift presentado FormSheet o página, aquí: "PageSheetViewController.swift"
class PageSheetViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidAppear(animated: Bool) {
let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleTapBehind:"))
recognizer.delegate = self
recognizer.numberOfTapsRequired = 1
recognizer.cancelsTouchesInView = false
self.view.window?.addGestureRecognizer(recognizer)
}
func gestureRecognizer(sender: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
func handleTapBehind(sender:UIGestureRecognizer) {
if(sender.state == UIGestureRecognizerState.Ended){
var location:CGPoint = sender.locationInView(nil)
// detect iOS Version 8.0 or greater
let Device = UIDevice.currentDevice()
let iosVersion = Double(Device.systemVersion) ?? 0
let iOS8 = iosVersion >= 8
if (iOS8) {
// in landscape view you will have to swap the location coordinates
if(UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)){
location = CGPointMake(location.y, location.x);
}
}
if(!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)){
self.view.window?.removeGestureRecognizer(sender)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
}
}