Jak wyłączyć gest przesuwania UIPageViewController?

125

W moim przypadku rodzic UIViewControllerzawiera UIPageViewControllerktóry zawiera UINavigationControllerktóry zawieraUIViewController . Muszę dodać gest przesunięcia do ostatniego kontrolera widoku, ale przesunięcia są obsługiwane tak, jakby należały do ​​kontrolera widoku strony. Próbowałem to zrobić zarówno programowo, jak i przez xib, ale bez rezultatu.

Więc jak rozumiem, nie mogę osiągnąć celu, dopóki nie poradzę UIPageViewControllersobie z jego gestami. Jak rozwiązać ten problem?

user2159978
źródło
7
UżyjpageViewControllerObject.view.userInteractionEnabled = NO;
Srikanth
6
niepoprawne, ponieważ blokuje również całą jego zawartość. Ale muszę zablokować tylko jego gesty
user2159978
Dlaczego to robisz? Jakiego zachowania chcesz od aplikacji? Wygląda na to, że zbytnio komplikujesz architekturę. Czy możesz wyjaśnić, do jakiego zachowania dążysz.
Fogmeister
Jedna ze stron w UIPageViewControllerma UINavigationController. Klient chce
uruchomić
@Srikanth dzięki, właśnie tego potrzebowałem! Mój widok strony ulegał awarii, gdy uruchomiłem programową zmianę strony i ktoś przerwał ją przeciąganiem, więc musiałem tymczasowo wyłączać przesuwanie za każdym razem, gdy zmiana strony jest uruchamiana w kodzie ...
Kuba Suder

Odpowiedzi:

262

Udokumentowanym sposobem zapobiegania UIPageViewControllerprzewijaniu jest nieprzypisywanie dataSourcewłaściwości. Jeśli przypiszesz źródło danych, przejdzie ono do trybu nawigacji opartego na gestach, czemu próbujesz zapobiec.

Bez źródła danych ręcznie udostępniasz kontrolery widoku, kiedy chcesz setViewControllers:direction:animated:completion metody, a na żądanie będą one przenoszone między kontrolerami widoku.

Powyższe można wywnioskować z dokumentacji firmy Apple dotyczącej UIPageViewController (Omówienie, drugi akapit):

Aby obsługiwać nawigację opartą na gestach, należy zapewnić kontrolery widoku przy użyciu obiektu źródła danych.

Jessedc
źródło
1
Ja też wolę to rozwiązanie. Możesz zapisać stare źródło danych w zmiennej, ustawić źródło danych na zero, a następnie przywrócić stare źródło danych, aby ponownie włączyć przewijanie. Nie wszystko to jest takie przejrzyste, ale znacznie lepsze niż przechodzenie przez podstawową hierarchię widoków w celu znalezienia widoku przewijania.
Matt R
3
Wyłączenie źródła danych powoduje problemy w odpowiedzi na powiadomienia z klawiatury dotyczące podglądów podrzędnych pól tekstowych. Iterowanie po widokach podrzędnych widoku w celu znalezienia UIScrollView wydaje się być bardziej niezawodne.
AR Younce
9
Czy to nie usuwa kropek na dole ekranu? A jeśli nie masz przesuwania ani kropek, nie ma sensu używać PageViewController w ogóle. Domyślam się, że pytający chce zachować kropki.
Leo Flaherty
Pomogło to rozwiązać problem, z którym miałem do czynienia, gdy strony są ładowane asynchronicznie, a użytkownik wykonuje gest machnięcia. Brawo :)
Ja͢ck
1
A. R Younce ma rację. Jeśli wyłączysz źródło danych PageViewController w odpowiedzi na powiadomienie z klawiatury (tj. Nie chcesz, aby użytkownik przewijał w poziomie, gdy klawiatura jest uniesiona), twoja klawiatura natychmiast zniknie, gdy wyzerujesz źródło danych PageViewController.
micnguyen
82
for (UIScrollView *view in self.pageViewController.view.subviews) {

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

        view.scrollEnabled = NO;
    }
}
user2159978
źródło
5
Ten zachowuje kontrolę nad stroną, co jest miłe.
Marcus Adams
1
To świetne podejście, ponieważ zachowuje kontrolę nad stroną (małe kropki). Będziesz chciał je zaktualizować podczas ręcznego wprowadzania zmian na stronie. Użyłem tej odpowiedzi dla tego stackoverflow.com/a/28356383/1071899
Simon Bøgh
1
Dlaczego niefor (UIView *view in self.pageViewController.view.subviews) {
dengApro
Należy pamiętać, że użytkownicy nadal będą mogli nawigować między stronami, dotykając kontrolki strony w lewej i prawej połowie.
MasterCarl
57

Tłumaczę odpowiedź user2159978 na Swift 5.1

func removeSwipeGesture(){
    for view in self.pageViewController!.view.subviews {
        if let subView = view as? UIScrollView {
            subView.isScrollEnabled = false
        }
    }
}
zawietrzny
źródło
30

Wdrażanie rozwiązania @ lee's (@ user2159978's) jako rozszerzenia:

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

Użycie: (w UIPageViewController)

self.isPagingEnabled = false
LinusGeffarth
źródło
Aby uzyskać bardziej elegancki wygląd, możesz dodać, że: var scrollView: UIScrollView? { for view in view.subviews { if let subView = view as? UIScrollView { return subView } } return nil }
Sprzedaż Wagner
Tutaj metoda pobierająca zwraca tylko włączony stan ostatniego widoku podrzędnego.
Andrew Duncan
8

Walczę z tym już od jakiegoś czasu i pomyślałem, że powinienem opublikować swoje rozwiązanie, kontynuując odpowiedź Jessedc; usuwanie źródła danych PageViewController.

Dodałem to do mojej klasy PgeViewController (połączonej z moim kontrolerem widoku strony w scenorysie, dziedziczy oba UIPageViewControlleri 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
    }
}

Można to następnie wywołać, gdy pojawi się każdy widok podrzędny (w tym przypadku, aby go wyłączyć);

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

Mam nadzieję, że to komuś pomoże, nie jest tak czyste, jak bym chciał, ale nie wydaje się zbyt hacky itp.

EDYCJA: Jeśli ktoś chce przetłumaczyć to na Objective-C, zrób :)

Jamie Robinson
źródło
8

Edytuj : ta odpowiedź działa tylko w przypadku stylu zawinięcia strony. Odpowiedź Jessedc jest znacznie lepsza: działa niezależnie od stylu i opiera się na udokumentowanym zachowaniu .

UIPageViewController ujawnia swój zestaw aparatów do rozpoznawania gestów, których można użyć do ich wyłączenia:

// myPageViewController is your UIPageViewController instance
for (UIGestureRecognizer *recognizer in myPageViewController.gestureRecognizers) {
    recognizer.enabled = NO;
}
Austin
źródło
8
Czy przetestowałeś swój kod? myPageViewController.gestureRecognizers jest zawsze puste
user2159978
11
wygląda na to, że Twoje rozwiązanie działa w przypadku stylu zawijania strony, ale w mojej aplikacji używam stylu widoku przewijania
user2159978
8
Nie działa przy przewijaniu w poziomie. Zwracana wartość jest zawsze pustą tablicą.
Khanh Nguyen
czy doszliście do tego w stylu przewijania?
Esqarrouth
4

Jeśli chcesz UIPageViewControllerzachować możliwość przesuwania, jednocześnie zezwalając kontrolkom zawartości na korzystanie z ich funkcji (przesuń, aby usunąć itp.), Po prostu wyłącz canCancelContentTouchesw UIPageViewController.

Umieścić to w swoim UIPageViewController„s viewDidLoadfunc. (Szybki)

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

Plik UIPageViewControllerMa automatycznie wygenerowane podrzędny, który obsługuje gesty. Możemy uniemożliwić tym podglądom anulowanie gestów treści.

Z...

Przesuń, aby usunąć w tableView, który znajduje się w pageViewController

Carter Medlin
źródło
Dzięki Carter. Wypróbowałem twoje rozwiązanie. Jednak nadal istnieją sytuacje, w których pageViewController zastępuje akcję przesunięcia poziomego poza widokiem tabeli podrzędnej. Jak mogę się upewnić, że kiedy użytkownik przesuwa palcem po podrzędnym widoku tabeli, widok tabeli ma zawsze priorytet, aby najpierw uzyskać gest?
David Liu,
3

Przydatne rozszerzenie UIPageViewControllerdo włączania i wyłączania machnięcia.

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
źródło
3

Rozwiązałem to w ten sposób (Swift 4.1)

if let scrollView = self.view.subviews.filter({$0.isKind(of: UIScrollView.self)}).first as? UIScrollView {
             scrollView.isScrollEnabled = false
}
Darko
źródło
3

Szybki sposób na odpowiedź @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
źródło
0

Podobnie jak @ user3568340 answer

Szybki 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
źródło
0

Dzięki odpowiedzi @ user2159978.

Sprawiam, że jest to trochę bardziej zrozumiałe.

- (void)disableScroll{
    for (UIView *view in self.pageViewController.view.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
            UIScrollView * aView = (UIScrollView *)view;
            aView.scrollEnabled = NO;
        }
    }
}
dengApro
źródło
0

Odpowiedzi, które znalazłem, wydają mi się bardzo zagmatwane lub niekompletne, więc oto kompletne i konfigurowalne rozwiązanie:

Krok 1:

Daj każdemu z elementów PVC odpowiedzialność za określenie, czy przewijanie w lewo i w prawo jest włączone, czy nie.

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

Każdy z kontrolerów widoku PVC powinien implementować powyższy protokół.

Krok 2:

W kontrolerach PVC wyłącz przewijanie, jeśli to konieczne:

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

class SomeViewController: UIViewController {
    // ...    
}

Krok 3:

Dodaj efektywne metody blokady przewijania do swojego 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
    }
}

Krok 4:

Zaktualizuj atrybuty związane z przewijaniem po przejściu do nowego ekranu (jeśli przejdziesz do jakiegoś ekranu ręcznie, nie zapomnij wywołać metody 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
źródło
0

Istnieje znacznie prostsze podejście, niż sugeruje większość odpowiedzi, które polega na zwróceniu nilw wywołaniach zwrotnych viewControllerBeforei viewControllerAfterźródła danych.

Spowoduje to wyłączenie gestu przewijania na urządzeniach z systemem iOS 11+, przy jednoczesnym zachowaniu możliwości używania dataSource(np. presentationIndex/presentationCount Używany jako wskaźnik strony)

Wyłącza również nawigację za pośrednictwem. the pageControl(kropki na dole)

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
źródło
0

pageViewController.view.isUserInteractionEnabled = false

gutte
źródło
-1

Tłumaczenie odpowiedzi @ user2159978 na C #:

foreach (var view in pageViewController.View.Subviews){
   var subView = view as UIScrollView;
   if (subView != null){
     subView.ScrollEnabled = enabled;
   }
}
gtrujillos
źródło
-1

(Swift 4) Możesz usunąć gestyRecognizers swojej stronyViewController:

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

Jeśli wolisz w przedłużeniu:

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

i pageViewController.removeGestureRecognizers

Miguel
źródło
-1

Zadeklaruj to w ten sposób:

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

Następnie użyj tego w ten sposób:

scrollView?.isScrollEnabled = true //false
Bartłomiej Semańczyk
źródło
-1

Wyliczenie podglądów podrzędnych w celu znalezienia scrollView a UIPageViewControllernie działa dla mnie, ponieważ nie mogę znaleźć żadnego scrollView w mojej podklasie kontrolera strony. Pomyślałem więc o wyłączeniu funkcji rozpoznawania gestów, ale na tyle ostrożnie, aby nie wyłączać niezbędnych.

Więc wymyśliłem to:

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

Umieść to w środku viewDidLoad()i gotowe!

Glenn
źródło
-1
 override func viewDidLayoutSubviews() {
    for View in self.view.subviews{
        if View.isKind(of: UIScrollView.self){
            let ScrollV = View as! UIScrollView
            ScrollV.isScrollEnabled = false
        }

    }
}

Dodaj to do swojej klasy pageviewcontroller. 100% sprawne

ap00724
źródło
proszę o komentarz, z jakim problemem masz do czynienia ten kod działa idealnie: /
ap00724
-1

po prostu dodaj tę właściwość kontrolną do swojej podklasy UIPageViewController :

var isScrollEnabled = true {
    didSet {
        for case let scrollView as UIScrollView in view.subviews {
            scrollView.isScrollEnabled = isScrollEnabled
        }
    }
}
Stanislau Baranouski
źródło