Pergunta

Quero descartar um controlador de visualização modal de criação de formulário quando o usuário toca fora da visualização modal ... Eu já vi um monte de aplicativos fazendo isso (eBay no iPad, por exemplo), mas não consigo descobrir como, já que as vistas por baixo são desativadas de toques de toques Quando as visualizações modais são exibidas assim (elas estão apresentando como uma popover, talvez?) ... alguém tem alguma sugestão?

Foi útil?

Solução

Estou um ano atrasado, mas isso é bastante simples de fazer.

Peça ao seu controlador de visualização modal conectar um reconhecedor de gestos à janela da 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];

O código de manuseio:

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

É sobre isso. Seja condenado, este é um comportamento útil e muitas vezes intuitivo.

Outras dicas

Os outros aplicativos não estão usando visualizações modais se permitirem que a visualização seja descartada clicando fora dela. UIModalPresentationFormSheets não pode ser demitido dessa maneira. (Nem, de fato, qualquer uimodal em SDK3.2). Apenas o UIPopoverController pode ser descartado clicando fora da área. É muito possível (embora contra o iPad hig da Apple) para o desenvolvedor de aplicativos ter sombreado a tela de fundo e depois exibir o UIPopoverController para que pareça um UIModalPresentationFormSheets (ou outra visão uimodal).

...] O estilo uimodalTesentationCurrentContext permite que um controlador de visualização adote o estilo de apresentação de seus pais. Em cada visão modal, as áreas diminuídas mostram o conteúdo subjacente, mas não permitem torneiras nesse conteúdo. Portanto, diferentemente de um popover, suas visualizações modais ainda devem ter controles que permitam ao usuário descartar a visualização modal.

Consulte o IpadProgrammingGuide no site do desenvolvedor para obter mais informações (página 46 - "Configurando o estilo de apresentação para visualizações modais")

Para iOS 8, vocês dois devem implementar o UIGestureRecognizer, e trocar as coordenadas (x, y) da localização tocada quando estiver na orientação da paisagem. Não tenho certeza se isso se deve a um bug do 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;
}

O código acima funciona muito bem, mas eu mudaria a declaração se,

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

Isso garante que você ainda possa interagir com a barra de navegação, caso contrário, tocar nela descarta a visualização modal.

Resposta atualizada para iOS 8

Aparentemente, no iOS 8, o UIDimmingView Possui um reconhecedor de gestos de toque, que interfere na implementação inicial, por isso o ignoramos e não exigimos que ele falhe.


Essa é a idade da velocidade, então a maioria provavelmente está apenas copiando o código acima. Mas, infelizmente, eu sofro de TOC quando se trata de código.

Aqui está uma solução modular que usa a resposta de Danilo Campos com as categorias. Isso também resolve um bug importante que pode ocorrer se você estiver descartando seu modal por outros meios, como mencionado.

NOTA: As instruções IF estão lá porque eu uso o controlador de exibição para iPhone e iPad, e apenas o iPad precisa se registrar/não registrar.

ATUALIZAR: A essência foi atualizada, pois não funcionou corretamente com o incrível Fcoverlay código, e não permitiu que os gestos fossem reconhecidos na visão apresentada. Esses problemas são corrigidos. Usar a categoria é tão fácil quanto:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (self.presentingViewController) {
        [self registerForDismissOnTapOutside];
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    if (self.presentingViewController) {
        [self unregisterForDismissOnTapOutside];
    }

    [super viewWillDisappear:animated];
}

Copie cola este código no seu 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: Se você tem outra maneira de fechar o seu Janela pop -up modal, não se esqueça de remover o reconhecedor do gesto da torneira!

Esqueci isso e tive acidentes malucos mais tarde, já que o reconhecedor da torneira ainda estava disparando eventos.

Acreditando o iOS HIG da Apple, 1. A visão modal não tem essa capacidade de ser descartada sem nenhuma opinião sobre si mesma; 2. Use a visão modal na situação em que uma entrada do usuário é necessária.

Use uiPresentationController:

- (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 no exemplo do Lookinside

Isso funciona para mim para iOS7 e 8 e barra de navegação.

Se você não precisar da barra NAV, remova o local2 e a segunda condição na instrução IF após os tubos.

@Miquel Isso deve funcionar para você também

- (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: Você também pode precisar ser um delegado de reconhecimento de gestos para que esta e outras soluções acima funcionem. Faça assim:

@interface CommentTableViewController () <UIGestureRecognizerDelegate>

Defina -se como o delegado para o reconhecedor:

self.recognizer.delegate = self;

e implementar este método delegado:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}

É bem factível.

Dê uma olhada aqui

https://stackoverflow.com/a/26016458/4074557

É um Controlador de navegação (modal) que descarta automaticamente o iPad (quando você toca para fora)

Use seu ViewController dentro dele.

Espero que ajude.

Eu sei que é tarde, mas considere usar o CleanModal (testado com iOS 7 e 8).

https://github.com/orafaelreis/cleanmodal

Você pode usar MzformSheetController assim:

MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];

No Swift 2/Xcode versão 7.2 (7C68), o código a seguir funcionou para mim.

ATENÇÃO: Este código deve ser colocado no arquivo ViewController.swift da folha de formulário ou página apresentada: "PagSheetViewController.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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top