web-dev-qa-db-fra.com

Iphone SDK rejetant ViewControllers modaux sur ipad en cliquant à l'extérieur

Je souhaite supprimer un contrôleur de vue modale FormSheetPresentation lorsque l'utilisateur appuie en dehors de la vue modale ... J'ai vu un tas d'applications le faire (eBay sur ipad par exemple), mais je ne peux pas comprendre comment, puisque les vues inférieures sont désactivées quand les vues modales sont affichées comme ceci (le présente-t-il comme une popover peut-être?) ... Quelqu'un a-t-il des suggestions?

53
Daniel

J'ai un an de retard, mais c'est assez simple à faire.

Demandez à votre contrôleur de vue modale de joindre un identificateur de geste à la fenêtre de la vue:

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

Le code de manutention:

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

C'est à peu près ça. HIG soit damné, c’est un comportement utile et souvent intuitif.

127
Danilo Campos

Les autres applications n'utilisent pas de vues modales si elles permettent de fermer la vue en cliquant en dehors de celle-ci. UIModalPresentationFormSheets ne peut pas être rejeté de cette façon. (ni, en effet, aucun UIModal dans SDK3.2). Seul le UIPopoverController peut être ignoré en cliquant en dehors de la zone. Il est très possible (bien que contre le iPad HIG d’Apple) que le développeur de l’application ait ombré l’écran d’arrière-plan, puis affiché la UIPopoverController pour qu’elle ressemble à une UIModalPresentationFormSheets (ou autre vue UIModal). 

[...] Le style UIModalPresentationCurrentContext permet à un contrôleur de vue d'adopter le style de présentation de son parent. Dans chaque vue modale, les zones grisées affichent le contenu sous-jacent, mais n'autorisent pas les entailles dans ce contenu. Par conséquent, contrairement à un popover, vos vues modales doivent toujours disposer de contrôles permettant à l'utilisateur de rejeter la vue modale.

Pour plus d'informations, consultez le Guide de programmation iPad sur le site du développeur (Page 46 - "Configuration du style de présentation pour les vues modales").

11
Jann

Pour iOS 8, vous devez à la fois implémenter la UIGestureRecognizer et permuter les coordonnées (x, y) de l'emplacement sélectionné lorsque vous êtes en mode paysage. Pas sûr que cela soit dû à un bug 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;
}
11
Erich

Le code ci-dessus fonctionne très bien, mais je changerais l'instruction if en,

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

Cela garantit que vous pouvez toujours interagir avec la barre de navigation. Sinon, taper dessus ferait disparaître la vue modale.

10
Keith

Réponse mise à jour pour iOS 8

Apparemment, dans iOS 8, la UIDimmingView possède un identificateur de gestes de tapotement qui interfère avec la mise en œuvre initiale. Nous l'ignorons donc et nous ne l'exigeons pas.


C'est l'âge de la vitesse, donc la plupart ne font probablement que copier le code ci-dessus. Mais, malheureusement, je souffre d'OCD en ce qui concerne le code.

Voici une solution modulaire qui utilise la réponse de Danilo Campos avec les catégories . Cela résout également résout un bogue important qui peut survenir si vous rejetez votre modal par un autre moyen, comme mentionné .

REMARQUE: Les instructions if sont présentes car j'utilise le contrôleur de vue pour iPhone et iPad, et seul l'iPad doit s'inscrire/désenregistrer.

UPDATE: Gist a été mis à jour, car il ne fonctionnait pas correctement avec le code génial FCOverlay , et il ne permettait pas de reconnaître les gestes dans la vue présentée. Ces problèmes sont résolus. Utiliser cette catégorie est aussi simple que:

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

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

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

    [super viewWillDisappear:animated];
}
9
Mazyod

Copier coller ce code dans votre 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];
        }
    }
}
8
Samidjo

Very important: Si vous avez un autre moyen de fermer votre fenêtre contextuelle modale , n'oubliez pas de supprimer le dispositif de reconnaissance des gestes du tap! 

J'ai oublié cela et j'ai eu des collisions folles plus tard, car le logiciel de reconnaissance de claquettes déclenchait toujours des événements.

3
foddex

Selon iOS HIG d’Apple, 1. la vue modale n’a pas cette possibilité d’être rejetée sans aucune contribution de sa part; 2. utilisez la vue modale dans le cas où une entrée de l'utilisateur est requise. 

2
Raymond Wang

Cela fonctionne pour moi pour iOS 7 un 8 et la barre de navigation.

Si vous n'avez pas besoin de la barre de navigation, supprimez simplement location2 et la deuxième condition de l'instruction if après les tubes.

@MiQUEL cela devrait fonctionner pour vous aussi

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

Éditer: Vous devrez peut-être aussi être un délégué de reconnaissance de mouvements pour que cela et d'autres solutions ci-dessus fonctionnent. Faites-le comme ça:

@interface CommentTableViewController () <UIGestureRecognizerDelegate>

définissez-vous en tant que délégué pour le programme de reconnaissance:

self.recognizer.delegate = self;

et implémenter cette méthode déléguée:

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

Utilisez UIPresentationController à la place:

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

Modifié à partir de l'exemple LookInside

1
yhlin

C'est assez faisable.

Regardez ici

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

Il s’agit d’un NavigationController (modal) qui se ferme automatiquement pour ipad (lorsque vous touchez à l’extérieur)

Utilisez votre viewcontroller à l'intérieur.

J'espère que ça aide.

0
Cjs

Je sais qu'il est tard, mais envisagez d'utiliser CleanModal (testé avec iOS 7 et 8).

https://github.com/orafaelreis/CleanModal

0
orafaelreis

Dans Swift 2/Xcode Version 7.2 (7C68), le code suivant fonctionnait pour moi.

Attention: ce code doit être placé dans le fichier ViewController.Swift de la feuille de calcul ou de la feuille de page présentée, ici: "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)
            }
        }
    }
}
0
Roland Krinner

Vous pouvez utiliser MZFormSheetController comme ceci:

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