web-dev-qa-db-fra.com

UIPageViewController Gestionneurs de gestes

Je travaille sur une application depuis quelque temps déjà, mais je ne pouvais pas poser cette question à cause de la NDA. 

J'ai une charge UIPageViewController avec mon Viewcontroller. Les contrôleurs de vue ont des boutons qui sont remplacés par les dispositifs de reconnaissance de mouvements PageViewControllers. Par exemple, j'ai un bouton sur le côté droit du contrôleur de vue et lorsque vous appuyez sur le bouton, PageViewController prend le relais et modifie la page.

Comment puis-je faire en sorte que le bouton reçoive le toucher et annule la reconnaissance des gestes dans PageViewController?

Je pense que PageViewController fait de mon ViewController une sous-vue de sa vue.

Je sais que je pourrais désactiver tous les gestes, mais ce n'est pas l'effet que je recherche.

Je ne voudrais pas préférer sous-classer le PageViewController car Apple dit que cette classe n'est pas destinée à être sous-classée. 

54
Rich86man

Vous pouvez remplacer 

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
 shouldReceiveTouch:(UITouch *)touch

pour mieux contrôler quand le PageViewController doit recevoir le contact et non. Examinez "Empêcher les reconnaisseurs de geste d’analyser les contacts" dans Reconnaissance de gestes API de développement

Ma solution ressemble à ceci dans RootViewController pour UIPageViewController:

Dans viewDidLoad:

//EDITED Need to take care of all gestureRecogizers. Got a bug when only setting the delegate for Tap
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
    gR.delegate = self;
}

La dérogation:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    //Touch gestures below top bar should not make the page turn.
    //EDITED Check for only Tap here instead.
    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        CGPoint touchPoint = [touch locationInView:self.view];
        if (touchPoint.y > 40) {
            return NO;
        }
        else if (touchPoint.x > 50 && touchPoint.x < 430) {//Let the buttons in the middle of the top bar receive the touch
            return NO;
        }
    }
    return YES;
}

Et n'oubliez pas de définir le RootViewController comme UIGestureRecognizerDelegate.

(Pour info, je ne suis qu'en mode paysage.)

EDIT - Le code ci-dessus traduit en Swift 2:

Dans viewDidLoad:

for gr in self.view.gestureRecognizers! {
    gr.delegate = self
}

Faites en sorte que le contrôleur de vue de page hérite de UIGestureRecognizerDelegate puis ajoutez:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if let _ = gestureRecognizer as? UITapGestureRecognizer {
        let touchPoint = touch .locationInView(self.view)
        if (touchPoint.y > 40 ){
            return false
        }else{
            return true
        }
    }
    return true
}
50
jramer

Voici une autre solution, qui peut être ajoutée dans le modèle viewDidLoad juste après la partie self.view.gestureRecognizers = self.pageViewController.gestureRecognizers à partir du modèle Xcode. Cela évite de jouer avec les tripes des détecteurs de geste ou de traiter avec ses délégués. Il supprime simplement le dispositif de reconnaissance des gestes du toucher des vues, ne laissant que le dispositif de reconnaissance par balayage. 

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

// Find the tap gesture recognizer so we can remove it!
UIGestureRecognizer* tapRecognizer = nil;    
for (UIGestureRecognizer* recognizer in self.pageViewController.gestureRecognizers) {
    if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
        tapRecognizer = recognizer;
        break;
    }
}

if ( tapRecognizer ) {
    [self.view removeGestureRecognizer:tapRecognizer];
    [self.pageViewController.view removeGestureRecognizer:tapRecognizer];
}

Maintenant, pour basculer entre les pages, vous devez glisser. Les taps ne fonctionnent maintenant que sur vos contrôles en haut de la page (ce que je cherchais).

54
Pat McG

J'ai eu le même problème. L'exemple et la documentation le font dans loadView ou viewDidLoad:

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

Ceci remplace les identifiants de mouvements des vues UIViewControllers par les reconnaisseurs de gesture de UIPageViewController. Désormais, lorsqu'une intervention tactile est effectuée, ils sont d'abord envoyés via les dispositifs de reconnaissance de mouvements pageViewControllers - s'ils ne correspondent pas, ils sont envoyés aux sous-vues.

Décommentez cette ligne et tout fonctionne comme prévu.

Phillip

4
Phillip

La définition du délégué gestureRecognizers sur un viewController comme ci-dessous ne fonctionne plus sur ios6.

for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
    gR.delegate = self;
}

Dans ios6, le fait de définir le délégué gestureRecognizers de votre pageViewController dans un viewController provoque un blocage

3
noRema

Dans les versions plus récentes (je suis dans Xcode 7.3 et ciblant iOS 8.1+), aucune de ces solutions ne semble fonctionner.

La réponse acceptée planterait avec une erreur: 

La reconnaissance de gestes de panoramique intégrée dans UIScrollView doit avoir sa vue de défilement en tant que son délégué.

La réponse la mieux classée actuellement (de Pat McG) ne fonctionne plus aussi, car la vue de défilement de UIPageViewController semble utiliser des sous-classes de reconnaissance de mouvements impairs que vous ne pouvez pas rechercher. Par conséquent, l'instruction if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) ne s'exécute jamais.

J'ai choisi simplement de définir la valeur false sur cancelsTouchesInView de chaque outil de reconnaissance, ce qui permet aux sous-vues de la variable UIPageViewController de recevoir des contacts.

Dans viewDidLoad:

guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else {
    print("No gesture recognizers on scrollview.")
    return
}

for recognizer in recognizers {
    recognizer.cancelsTouchesInView = false
}

J'ai utilisé 

for (UIScrollView *view in _pageViewController.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]]) {
        view.delaysContentTouches = NO;
    }
}

permettre aux clics d'accéder aux boutons à l'intérieur d'un UIPageViewController

2
Adam Johns

Si vous utilisez un bouton que vous avez sous-classé, vous pouvez remplacer touchesBegan, touchesMoved, et touchEnded, en invoquant votre propre page programmatique à son tour, mais sans appeler super ni passer au toucher la chaîne de notification. 

0
isaac

Je crée régulièrement des contrôleurs de page pendant que mon utilisateur saute, se courbe et se glisse vers différentes vues de page. Dans la routine qui crée un nouveau contrôleur de pageview, j'utilise une version légèrement plus simple de l'excellent code présenté ci-dessus:

UIPageViewController *npVC = [[UIPageViewController alloc]
                       initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                       navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                       options: options];
...

// Find the pageView tap gesture recognizer so we can remove it!
for (UIGestureRecognizer* recognizer in npVC.gestureRecognizers) {
    if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
        UIGestureRecognizer* tapRecognizer = recognizer;
        [npVC.view removeGestureRecognizer:tapRecognizer];
       break;
    }
}

Maintenant, les taps fonctionnent comme je le souhaite (avec les tapotements gauche et droit sautant d'une page, et les boucles fonctionnent bien).

0
Bill Cheswick

Dans mon cas, je voulais désactiver le tapotement sur UIPageControl et laisser le tapotement être reçu par un autre bouton à l'écran. Balayer fonctionne toujours. J'ai essayé de nombreuses manières et je pense que c'était la solution la plus simple:

for (UIPageControl *view in _pageController.view.subviews) {
    if ([view isKindOfClass:[UIPageControl class]]) {
        view.enabled = NO;
    }
}

Cela consiste à obtenir la vue UIPageControl à partir des sous-vues UIPageController et à désactiver l'interaction utilisateur.

0
Leto Baxevanaki

Il vous suffit de créer une sous-vue (liée à un nouveau gestesView IBOutlet) dans votre RootViewController et d’affecter les gestes à cette nouvelle vue. Cette vue couvre la partie de l'écran que vous souhaitez activer.

in viewDidLoad change:

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

à :

self.gesturesView.gestureRecognizers = self.pageViewController.gestureRecognizers;
0
tdf

Extension Swift 3 pour retirer le dispositif de reconnaissance de robinet:

    import UIKit

extension UIPageViewController {
    func removeTapRecognizer() {
        let gestureRecognizers = self.gestureRecognizers

        var tapGesture: UIGestureRecognizer?
        gestureRecognizers.forEach { recognizer in
            if recognizer.isKind(of: UITapGestureRecognizer.self) {
                tapGesture = recognizer
            }
        }
        if let tapGesture = tapGesture {
            self.view.removeGestureRecognizer(tapGesture)
        }
    }
}
0
Roman Barzyczak

C’est la solution qui a fonctionné le mieux pour moi et pour laquelle j’ai essayé de répondre. J’ai essayé de répondre, sauf que j’obtiendrais une erreur en cas de recherche de personnes au-delà des limites (page -1 ou page 23 pour moi) 

La solution PatMCG ne m'a pas donné suffisamment de flexibilité, car elle a annulé tous les reconnaissances, je voulais toujours le tap mais pas dans mon label

Dans mon UILabel, je remplaçais simplement comme suit, ce tap annulé pour mon label uniquement

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
 if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
     return NO;
 } else {
     return YES;
 }
}
0
Ryan Heitner

J'ai élaboré une solution de travail. Ajoutez un autre UIGestureRecognizer à UIPageViewController et implémentez la méthode déléguée fournie ci-dessous . À chaque instant où vous devez déterminer quel geste doit être verrouillé ou passé, cette méthode sera appelée. N'oubliez pas de fournir une référence à confictingView, qui dans mon cas était UITableView, qui reconnaît également le mouvement panoramique. Cette vue a été placée dans UIPageViewController, ainsi un geste de panoramique a été reconnu deux fois ou juste de manière aléatoire. Maintenant, dans cette méthode, je vérifie si le geste panoramique est à la fois dans mon UITableView et mon UIPageViewController, et je décide que UIPanGestureRecognizer est primaire.

Cette approche ne remplace pas directement l'un des autres outils de reconnaissance de geste, nous n'avons donc pas à nous soucier de la mention 'NSInvalidArgumentException'. 

Gardez à l'esprit que ce modèle n'est pas approuvé par Apple :)

 var conflictingView:UIView?
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if otherGestureRecognizer.view === pageViewController?.view {

            if let view = conflictingView {
                var point = otherGestureRecognizer.location(in: self.view)
                if view.frame.contains(point) {
                    print("Touch in conflicting view")
                    return false
                }
            }
            print("Touch outside conficting view")
            return true
        }
        print("Another view passed out")
        return true
    }

Je me suis retrouvé ici en cherchant un moyen simple et fourre-tout de répondre aux taps de mes contrôleurs de vue enfant au sein d'un UIPageViewController. Le cœur de ma solution (Swift 4, Xcode 9) a fini par être aussi simple que cela dans mon RootViewController.Swift (même structure que le modèle "Page-Based App" de Xcode):

override func viewDidLoad() {
    super.viewDidLoad()

    ...

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(pageTapped(sender:)))
    self.pageViewController?.view.subviews[0].addGestureRecognizer(tapGesture)
}

...

@objc func pageTapped(sender: UITapGestureRecognizer) {
    print("pageTapped")
}

(J'ai également utilisé cette réponse pour me permettre de savoir quelle page a été exploitée, c'est-à-dire la page actuelle.)

0
brookinc

Peut également utiliser ceci (merci pour l'aide, avec par exemple à propos de délégué):

// add UIGestureRecognizerDelegate

NSPredicate *tp = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [UITapGestureRecognizer class]];
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:tp][0];
tgr.delegate = self; // tap delegating

NSPredicate *pp = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [UIPanGestureRecognizer class]];
UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:pp][0];
pgr.delegate = self; // pan delegating

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 
{
    CGPoint touchPoint = [touch locationInView:self.view];

    if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 915 ) {
        return NO; // if y > 915 px in portrait mode
    }

    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 680 ) {
        return NO; // if y > 680 px in landscape mode
    }

    return YES;
}

Fonctionne parfaitement pour moi :)

0
iTux