Comment désactiver le geste de balayage de UIPageViewController?

125

Dans mon cas, le parent UIViewControllercontient UIPageViewControllerqui contient UINavigationControllerqui contient UIViewController. Je dois ajouter un geste de balayage au dernier contrôleur de vue, mais les balayages sont gérés comme s'ils appartenaient au contrôleur de vue de page. J'ai essayé de le faire à la fois par programme et via xib mais sans résultat.

Donc, si je comprends bien, je ne peux pas atteindre mon objectif tant que je n'ai pas UIPageViewControllergéré ses gestes. Comment résoudre ce problème?

user2159978
la source
7
UtilisationpageViewControllerObject.view.userInteractionEnabled = NO;
Srikanth
6
incorrect, car il bloque également tout son contenu. Mais je dois bloquer uniquement ses gestes
user2159978
Pourquoi fais-tu ça? Quel comportement voulez-vous de l'application? On dirait que vous compliquez trop l'architecture. Pouvez-vous expliquer le comportement que vous visez.
Fogmeister
L'une des pages de UIPageViewControllera UINavigationController. Le client souhaite afficher le contrôleur de navigation lors du geste de balayage (si cela est possible), mais le contrôleur de vue de page gère les balayages à la place
user2159978
@Srikanth merci, c'est exactement ce dont j'avais besoin! Ma page vue plantait lorsque j'ai déclenché un changement de page par programme et que quelqu'un l'a interrompu avec un balayage, j'ai donc dû désactiver temporairement le balayage à chaque fois que le changement de page est déclenché dans le code ...
Kuba Suder

Réponses:

262

La manière documentée d'empêcher le UIPageViewControllerdéfilement est de ne pas affecter la dataSourcepropriété. Si vous attribuez la source de données, elle passera en mode de navigation `` gestuelle '', ce que vous essayez d'empêcher.

Sans source de données, vous fournissez manuellement des contrôleurs de vue lorsque vous le souhaitez avec setViewControllers:direction:animated:completionméthode et il se déplacera entre les contrôleurs de vue à la demande.

Ce qui précède peut être déduit de la documentation Apple de UIPageViewController (Présentation, deuxième paragraphe):

Pour prendre en charge la navigation basée sur les gestes, vous devez fournir vos contrôleurs de vue à l'aide d'un objet de source de données.

Jessedc
la source
1
Je préfère également cette solution. Vous pouvez enregistrer l'ancienne source de données dans une variable, définir la source de données sur nil, puis restaurer l'ancienne source de données pour réactiver le défilement. Pas tout à fait propre, mais bien mieux que de parcourir la hiérarchie de vues sous-jacente pour trouver la vue de défilement.
Matt R
3
La désactivation de la source de données pose des problèmes lors de cette opération en réponse aux notifications clavier pour les sous-vues de champ de texte. Itérer sur les sous-vues de la vue pour trouver l'UIScrollView semble être plus fiable.
AR Younce
9
Cela ne supprime-t-il pas les points en bas de l'écran? Et si vous n'avez pas de balayage et pas de points, il n'y a pas grand-chose à utiliser un PageViewController. Je suppose que le demandeur veut conserver les points.
Leo Flaherty
Cela a aidé à résoudre un problème auquel j'étais confronté lorsque les pages sont chargées de manière asynchrone et que l'utilisateur fait un geste de balayage. Bravo :)
Ja͢ck
1
A. R Younce est correct. Si vous désactivez la source de données du PageViewController en réponse à une notification du clavier (c'est-à-dire: vous ne voulez pas que l'utilisateur défile horizontalement lorsque le clavier est relevé), votre clavier disparaîtra immédiatement lorsque vous supprimerez la source de données du PageViewController.
micnguyen
82
for (UIScrollView *view in self.pageViewController.view.subviews) {

    if ([view isKindOfClass:[UIScrollView class]]) {

        view.scrollEnabled = NO;
    }
}
user2159978
la source
5
Celui-ci garde le contrôle de la page, ce qui est bien.
Marcus Adams
1
C'est une excellente approche car elle garde le contrôle de la page (petits points). Vous voudrez les mettre à jour lors de la modification manuelle de la page. J'ai utilisé cette réponse pour ce stackoverflow.com/a/28356383/1071899
Simon Bøgh
1
Pourquoi pasfor (UIView *view in self.pageViewController.view.subviews) {
dengApro
Veuillez noter que les utilisateurs pourront toujours naviguer entre les pages en appuyant sur le contrôle de page dans la moitié gauche et droite.
MasterCarl
57

Je traduis la réponse de l' utilisateur2159978 en Swift 5.1

func removeSwipeGesture(){
    for view in self.pageViewController!.view.subviews {
        if let subView = view as? UIScrollView {
            subView.isScrollEnabled = false
        }
    }
}
Lee
la source
30

Implémentation de la solution de @ lee (@ user2159978) en tant qu'extension:

extension UIPageViewController {
    var isPagingEnabled: Bool {
        get {
            var isEnabled: Bool = true
            for view in view.subviews {
                if let subView = view as? UIScrollView {
                    isEnabled = subView.isScrollEnabled
                }
            }
            return isEnabled
        }
        set {
            for view in view.subviews {
                if let subView = view as? UIScrollView {
                    subView.isScrollEnabled = newValue
                }
            }
        }
    }
}

Utilisation: (dans UIPageViewController)

self.isPagingEnabled = false
LinusGeffarth
la source
Pour être plus élégant, vous pouvez ajouter que: var scrollView: UIScrollView? { for view in view.subviews { if let subView = view as? UIScrollView { return subView } } return nil }
Wagner Sales
Le getter ici ne renvoie que l'état activé de la dernière sous-vue.
Andrew Duncan
8

Je me bats ça depuis un moment maintenant et j'ai pensé que je devrais publier ma solution, suite à la réponse de Jessedc; suppression de la source de données du PageViewController.

J'ai ajouté ceci à ma classe PgeViewController (lié à mon contrôleur de vue de page dans le storyboard, hérite à la fois UIPageViewControlleret UIPageViewControllerDataSource):

static func enable(enable: Bool){
    let appDelegate  = UIApplication.sharedApplication().delegate as! AppDelegate
    let pageViewController = appDelegate.window!.rootViewController as! PgeViewController
    if (enable){
        pageViewController.dataSource = pageViewController
    }else{
        pageViewController.dataSource = nil
    }
}

Cela peut alors être appelé lorsque chaque sous-vue apparaît (dans ce cas pour la désactiver);

override func viewDidAppear(animated: Bool) {
    PgeViewController.enable(false)
}

J'espère que cela aide quelqu'un, ce n'est pas aussi propre que je le voudrais mais ne semble pas trop piraté, etc.

EDIT: Si quelqu'un veut traduire cela en Objective-C, veuillez le faire :)

Jamie Robinson
la source
8

Modifier : cette réponse ne fonctionne que pour le style de page curl. La réponse de Jessedc est bien meilleure: fonctionne quel que soit le style et repose sur un comportement documenté .

UIPageViewController expose son tableau de reconnaissance de gestes, que vous pouvez utiliser pour les désactiver:

// myPageViewController is your UIPageViewController instance
for (UIGestureRecognizer *recognizer in myPageViewController.gestureRecognizers) {
    recognizer.enabled = NO;
}
Austin
la source
8
Avez-vous testé votre code? myPageViewController.gestureRecognizers est toujours vide
user2159978
11
il semble que votre solution fonctionne pour le style de page curl, mais dans mon application, j'utilise le style d'affichage de défilement
user2159978
8
Ne fonctionne pas pour le défilement horizontal. La valeur renvoyée est toujours un tableau vide.
Khanh Nguyen
avez-vous trouvé cela pour le style de défilement?
Esqarrouth
4

Si vous souhaitez UIPageViewControllerconserver sa capacité à faire glisser, tout en permettant à vos contrôles de contenu d'utiliser leurs fonctionnalités (Glisser pour supprimer, etc.), désactivez simplement canCancelContentTouchesle UIPageViewController.

Mettez dans votre UIPageViewControllerde » viewDidLoadla fonct. (Rapide)

if let myView = view?.subviews.first as? UIScrollView {
    myView.canCancelContentTouches = false
}

Le UIPageViewControllerpossède une sous-vue générée automatiquement qui gère les gestes. Nous pouvons empêcher ces sous-vues d'annuler les gestes de contenu.

De...

Balayez pour supprimer sur une tableView qui se trouve à l'intérieur d'une pageViewController

Carter Medlin
la source
Merci Carter. J'ai essayé votre solution. Cependant, il arrive que pageViewController remplace l'action de balayage horizontal loin de la vue de la table enfant. Comment puis-je m'assurer que lorsque l'utilisateur glisse sur la vue de la table enfant, la vue de la table est toujours prioritaire pour obtenir le geste en premier?
David Liu
3

Une extension utile UIPageViewControllerpour activer et désactiver le balayage.

extension UIPageViewController {

    func enableSwipeGesture() {
        for view in self.view.subviews {
            if let subView = view as? UIScrollView {
                subView.isScrollEnabled = true
            }
        }
    }

    func disableSwipeGesture() {
        for view in self.view.subviews {
            if let subView = view as? UIScrollView {
                subView.isScrollEnabled = false
            }
        }
    }
}
jbustamante
la source
3

Je l'ai résolu comme ça (Swift 4.1)

if let scrollView = self.view.subviews.filter({$0.isKind(of: UIScrollView.self)}).first as? UIScrollView {
             scrollView.isScrollEnabled = false
}
Darko
la source
3

Un moyen rapide pour la réponse @lee

extension UIPageViewController {
    var isPagingEnabled: Bool {
        get {
            return scrollView?.isScrollEnabled ?? false
        }
        set {
            scrollView?.isScrollEnabled = newValue
        }
    }

    var scrollView: UIScrollView? {
        return view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView
    }
}
Szymon W
la source
0

Similaire à @ user3568340 answer

Swift 4

private var _enabled = true
    public var enabled:Bool {
        set {
            if _enabled != newValue {
                _enabled = newValue
                if _enabled {
                    dataSource = self
                }
                else{
                    dataSource = nil
                }
            }
        }
        get {
            return _enabled
        }
    }
Emmanouil Nicolas
la source
0

Merci à la réponse de @ user2159978.

Je le rends un peu plus compréhensible.

- (void)disableScroll{
    for (UIView *view in self.pageViewController.view.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
            UIScrollView * aView = (UIScrollView *)view;
            aView.scrollEnabled = NO;
        }
    }
}
dengApro
la source
0

Les réponses que j'ai trouvées me semblent très déroutantes ou incomplètes, voici donc une solution complète et configurable:

Étape 1:

Donnez à chacun de vos éléments PVC la responsabilité de dire si le défilement gauche et droit est activé ou non.

protocol PageViewControllerElement: class {
    var isLeftScrollEnabled: Bool { get }
    var isRightScrollEnabled: Bool { get }
}
extension PageViewControllerElement {
    // scroll is enabled in both directions by default
    var isLeftScrollEnabled: Bool {
        get {
            return true
        }
    }

    var isRightScrollEnabled: Bool {
        get {
            return true
        }
    }
}

Chacun de vos contrôleurs de vue PVC doit implémenter le protocole ci-dessus.

Étape 2:

Dans vos contrôleurs PVC, désactivez le défilement si nécessaire:

extension SomeViewController: PageViewControllerElement {
    var isRightScrollEnabled: Bool {
        get {
            return false
        }
    }
}

class SomeViewController: UIViewController {
    // ...    
}

Étape 3:

Ajoutez les méthodes de verrouillage de défilement efficaces à votre PVC:

class PVC: UIPageViewController, UIPageViewDelegate {
    private var isLeftScrollEnabled = true
    private var isRightScrollEnabled = true
    // ...

    override func viewDidLoad() {
        super.viewDidLoad()
        // ...
        self.delegate = self
        self.scrollView?.delegate = self
    }
} 

extension PVC: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print("contentOffset = \(scrollView.contentOffset.x)")

        if !self.isLeftScrollEnabled {
            disableLeftScroll(scrollView)
        }
        if !self.isRightScrollEnabled {
            disableRightScroll(scrollView)
        }
    }

    private func disableLeftScroll(_ scrollView: UIScrollView) {
        let screenWidth = UIScreen.main.bounds.width
        if scrollView.contentOffset.x < screenWidth {
            scrollView.setContentOffset(CGPoint(x: screenWidth, y: 0), animated: false)
        }
    }

    private func disableRightScroll(_ scrollView: UIScrollView) {
        let screenWidth = UIScreen.main.bounds.width
        if scrollView.contentOffset.x > screenWidth {
            scrollView.setContentOffset(CGPoint(x: screenWidth, y: 0), animated: false)
        }
    }
}

extension UIPageViewController {
    var scrollView: UIScrollView? {
        return view.subviews.filter { $0 is UIScrollView }.first as? UIScrollView
    }
}

Étape 4:

Mettez à jour les attributs liés au défilement lorsque vous atteignez un nouvel écran (si vous passez manuellement à un écran, n'oubliez pas d'appeler la méthode enableScroll):

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    let pageContentViewController = pageViewController.viewControllers![0]
    // ...

    self.enableScroll(for: pageContentViewController)
}

private func enableScroll(for viewController: UIViewController) {
    guard let viewController = viewController as? PageViewControllerElement else {
        self.isLeftScrollEnabled = true
        self.isRightScrollEnabled = true
        return
    }

    self.isLeftScrollEnabled = viewController.isLeftScrollEnabled
    self.isRightScrollEnabled = viewController.isRightScrollEnabled

    if !self.isLeftScrollEnabled {
        print("Left Scroll Disabled")
    }
    if !self.isRightScrollEnabled {
        print("Right Scroll Disabled")
    }
}
Michael-Martinez
la source
0

Il existe une approche beaucoup plus simple que ce que la plupart des réponses suggèrent ici, qui consiste à revenir nildans les rappels viewControllerBeforeet viewControllerAfterdataSource.

Cela désactive le mouvement de défilement sur les appareils iOS 11+, tout en gardant la possibilité d'utiliser le dataSource(pour des choses telles que le presentationIndex/presentationCount utilisé pour l'indicateur de page)

Il désactive également la navigation via. le pageControl(les points en bas)

extension MyPageViewController: UIPageViewControllerDataSource {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        return nil
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        return nil
    }
}
Claus Jørgensen
la source
0

pageViewController.view.isUserInteractionEnabled = false

gutte
la source
-1

Traduction de la réponse de @ user2159978 en C #:

foreach (var view in pageViewController.View.Subviews){
   var subView = view as UIScrollView;
   if (subView != null){
     subView.ScrollEnabled = enabled;
   }
}
gtrujillos
la source
-1

(Swift 4) Vous pouvez supprimer gestureRecognizers de votre pageViewController:

pageViewController.view.gestureRecognizers?.forEach({ (gesture) in
            pageViewController.view.removeGestureRecognizer(gesture)
        })

Si vous préférez en extension:

extension UIViewController{
    func removeGestureRecognizers(){
        view.gestureRecognizers?.forEach({ (gesture) in
            view.removeGestureRecognizer(gesture)
        })
    }
}

et pageViewController.removeGestureRecognizers

Miguel
la source
-1

Déclarez-le comme ceci:

private var scrollView: UIScrollView? {
    return pageViewController.view.subviews.compactMap { $0 as? UIScrollView }.first
}

Ensuite, utilisez-le comme ceci:

scrollView?.isScrollEnabled = true //false
Bartłomiej Semańczyk
la source
-1

Énumérer les sous-vues pour trouver le scrollView d'un UIPageViewControllern'a pas fonctionné pour moi, car je ne trouve aucun scrollView dans ma sous-classe de contrôleur de page. Donc, ce que j'ai pensé à faire est de désactiver les outils de reconnaissance de gestes, mais avec suffisamment de prudence pour ne pas désactiver les outils nécessaires.

Alors je suis venu avec ceci:

if let panGesture = self.gestureRecognizers.filter({$0.isKind(of: UIPanGestureRecognizer.self)}).first           
    panGesture.isEnabled = false        
}

Mettez cela à l'intérieur viewDidLoad()et vous êtes prêt!

Glenn
la source
-1
 override func viewDidLayoutSubviews() {
    for View in self.view.subviews{
        if View.isKind(of: UIScrollView.self){
            let ScrollV = View as! UIScrollView
            ScrollV.isScrollEnabled = false
        }

    }
}

Ajoutez ceci dans votre classe pageviewcontroller. 100% de travail

ap00724
la source
s'il vous plaît commenter quel problème vous rencontrez ce code fonctionne parfaitement: /
ap00724
-1

ajoutez simplement cette propriété de contrôle à votre sous-classe UIPageViewController :

var isScrollEnabled = true {
    didSet {
        for case let scrollView as UIScrollView in view.subviews {
            scrollView.isScrollEnabled = isScrollEnabled
        }
    }
}
Stanislau Baranouski
la source