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?

¿Fue útil?

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).

https://github.com/orafaelreis/CleanModal

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)
            }
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top