Как отключить жест смахивания UIPageViewController?

125

В моем случае parent UIViewControllercontains, UIPageViewControllerкоторый содержит, UINavigationControllerкоторый содержит UIViewController. Мне нужно добавить жест смахивания к последнему контроллеру представления, но смахивания обрабатываются так, как будто они принадлежат контроллеру представления страницы. Я пытался сделать это как программно, так и через xib, но безрезультатно.

Итак, насколько я понимаю, я не могу достичь своей цели, пока не UIPageViewControllerсправлюсь с ее жестами. Как решить эту проблему?

user2159978
источник
7
UsepageViewControllerObject.view.userInteractionEnabled = NO;
Srikanth
6
неверно, потому что он также блокирует все его содержимое. Но мне нужно заблокировать только его жесты
user2159978 03
Почему вы это делаете? Какого поведения вы хотите от приложения? Похоже, вы слишком усложняете архитектуру. Можете ли вы объяснить, к какому поведению вы стремитесь?
Fogmeister 03
Одна из страниц в UIPageViewControllerрубрике UINavigationController. Заказчик хочет, чтобы контроллер навигации отображался жестом смахивания (если это возможно), но вместо этого обрабатывает контроллер просмотра страницы
user2159978
@Srikanth, спасибо, это именно то, что мне нужно! При просмотре моей страницы происходил сбой, когда я программно запускал смену страницы, и кто-то прервал ее смахиванием, поэтому мне пришлось временно отключать смахивание каждый раз, когда изменение страницы запускалось в коде ...
Куба Судер

Ответы:

263

Задокументированный способ предотвращения UIPageViewControllerпрокрутки - не назначать dataSourceсвойство. Если вы назначите источник данных, он перейдет в режим навигации на основе жестов, что вы пытаетесь предотвратить.

Без источника данных вы вручную предоставляете контроллеры представления, когда хотите, с помощью setViewControllers:direction:animated:completionметода, и он будет перемещаться между контроллерами представления по запросу.

Вышеизложенное можно вывести из документации Apple по UIPageViewController (Обзор, второй абзац):

Для поддержки навигации на основе жестов необходимо предоставить контроллеры представления с помощью объекта источника данных.

Jessedc
источник
1
Я тоже предпочитаю это решение. Вы можете сохранить старый источник данных в переменной, установить для источника данных значение nil, а затем восстановить старый источник данных, чтобы снова включить прокрутку. Не все так чисто, но гораздо лучше, чем обход базовой иерархии представлений в поисках прокрутки.
Matt R
3
Отключение источника данных вызывает проблемы, когда это делается в ответ на уведомления клавиатуры для вложенных представлений текстовых полей. Итерация по вложенным представлениям представления для поиска UIScrollView кажется более надежной.
AR Younce 01
9
Разве это не убирает точки внизу экрана? И если у вас нет прокрутки и точек, нет особого смысла в использовании PageViewController вообще. Я предполагаю, что спрашивающий хочет сохранить точки.
Лео Флаэрти,
Это помогло решить проблему, с которой я столкнулся, когда страницы загружаются асинхронно и пользователь делает жест смахивания. Браво :)
Ja͢ck
1
А. Р. Юнс прав. Если вы отключаете источник данных PageViewController в ответ на уведомление с клавиатуры (например, вы не хотите, чтобы пользователь выполнял горизонтальную прокрутку, когда клавиатура поднята), ваша клавиатура немедленно исчезнет, ​​когда вы обнуляете источник данных PageViewController.
micnguyen
82
for (UIScrollView *view in self.pageViewController.view.subviews) {

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

        view.scrollEnabled = NO;
    }
}
user2159978
источник
5
Этот сохраняет контроль над страницей, что приятно.
Маркус Адамс
1
Это отличный подход, поскольку он сохраняет контроль страницы (маленькие точки). Вы захотите обновить их при изменении страницы вручную. Я использовал этот ответ для этого stackoverflow.com/a/28356383/1071899
Simon Bøgh
1
Почему бы и нетfor (UIView *view in self.pageViewController.view.subviews) {
dengApro
Обратите внимание, что пользователи по-прежнему смогут перемещаться между страницами, касаясь элемента управления страницей в левой и правой половине.
MasterCarl
57

Перевожу ответ user2159978 на Swift 5.1

func removeSwipeGesture(){
    for view in self.pageViewController!.view.subviews {
        if let subView = view as? UIScrollView {
            subView.isScrollEnabled = false
        }
    }
}
подветренный
источник
30

Реализация решения @ lee (@ user2159978) как расширения:

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

Использование: (в UIPageViewController)

self.isPagingEnabled = false
LinusGeffarth
источник
Чтобы стать более элегантным, вы можете добавить следующее: var scrollView: UIScrollView? { for view in view.subviews { if let subView = view as? UIScrollView { return subView } } return nil }
Wagner Sales
Получатель здесь возвращает только включенный статус последнего подпредставления.
Эндрю Дункан
8

Я уже некоторое время борюсь с этим и подумал, что должен опубликовать свое решение, следуя ответу Джесседа; удаление источника данных PageViewController.

Я добавил это в свой класс PgeViewController (связанный с моим контроллером просмотра страницы в раскадровке, наследует оба UIPageViewControllerи 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
    }
}

Затем его можно вызвать при появлении каждого дополнительного представления (в данном случае для его отключения);

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

Я надеюсь, что это кому-то поможет, это не так чисто, как хотелось бы, но не кажется слишком хакерским и т. Д.

РЕДАКТИРОВАТЬ: Если кто-то хочет перевести это на Objective-C, сделайте это :)

Джейми Робинсон
источник
8

Изменить : этот ответ работает только для стиля загиба страницы. Ответ Джесседа намного лучше: работает независимо от стиля и полагается на задокументированное поведение .

UIPageViewController предоставляет свой массив распознавателей жестов, которые вы можете использовать для их отключения:

// myPageViewController is your UIPageViewController instance
for (UIGestureRecognizer *recognizer in myPageViewController.gestureRecognizers) {
    recognizer.enabled = NO;
}
Остин
источник
8
Вы тестировали свой код? myPageViewController.gestureRecognizers всегда пусто
user2159978 03
11
похоже, ваше решение работает для стиля
загиба
8
Не работает горизонтальная прокрутка. Возвращаемое значение всегда является пустым массивом.
Khanh Nguyen
вы, ребята, поняли это для стиля прокрутки?
Esqarrouth
4

Если вы хотите, чтобы у вас UIPageViewControllerсохранялась возможность пролистывания, при этом позволяя элементам управления контентом использовать свои функции (проведите пальцем для удаления и т. Д.), Просто выключите canCancelContentTouchesв UIPageViewController.

Поместите это в вашей UIPageViewController«S viewDidLoadFUNC. (Swift)

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

У UIPageViewControllerнего есть автоматически сгенерированное подвид, которое обрабатывает жесты. Мы можем запретить этим подпредставлениям отменять жесты содержимого.

Из...

Проведите пальцем по экрану, чтобы удалить tableView, который находится внутри pageViewController

Картер Медлин
источник
Спасибо, Картер. Я пробовал твое решение. Однако бывают случаи, когда pageViewController перехватывает действие горизонтального смахивания в сторону от представления дочерней таблицы. Как я могу гарантировать, что когда пользователь проводит пальцем по дочернему табличному представлению, табличному представлению всегда уделяется приоритетное внимание, чтобы получить жест первым?
Дэвид Лю
3

Полезное расширение UIPageViewControllerдля включения и отключения свайпа.

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
источник
3

Решил вот так (Swift 4.1)

if let scrollView = self.view.subviews.filter({$0.isKind(of: UIScrollView.self)}).first as? UIScrollView {
             scrollView.isScrollEnabled = false
}
Дарко
источник
3

Быстрый способ ответа @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
    }
}
Шимон В
источник
0

Подобно ответу @ user3568340

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
        }
    }
Эммануил Николя
источник
0

Благодаря ответу @ user2159978.

Я делаю это немного более понятным.

- (void)disableScroll{
    for (UIView *view in self.pageViewController.view.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
            UIScrollView * aView = (UIScrollView *)view;
            aView.scrollEnabled = NO;
        }
    }
}
dengApro
источник
0

Ответы, которые я нашел, кажутся мне очень запутанными или неполными, поэтому вот полное и настраиваемое решение:

Шаг 1:

Дайте каждому из ваших PVC-элементов ответственность определять, разрешена ли прокрутка влево и вправо или нет.

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

Каждый из ваших контроллеров представления PVC должен реализовывать вышеуказанный протокол.

Шаг 2:

В контроллерах PVC отключите прокрутку при необходимости:

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

class SomeViewController: UIViewController {
    // ...    
}

Шаг 3:

Добавьте эффективные методы блокировки прокрутки в свой 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
    }
}

Шаг 4:

Обновите атрибуты, связанные с прокруткой, при переходе на новый экран (если вы переходите на какой-то экран вручную, не забудьте вызвать метод 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")
    }
}
майкл-Martinez
источник
0

Существует гораздо более простой подход, чем предлагают здесь большинство ответов, а именно возврат nilв обратных вызовах viewControllerBeforeи viewControllerAfterdataSource.

Это отключает жест прокрутки на устройствах iOS 11+, сохраняя при этом возможность использовать dataSource(для таких вещей, как presentationIndex/ presentationCountиспользуется для индикатора страницы)

Он также отключает навигацию через. что pageControl(точки в нижней части )

extension MyPageViewController: UIPageViewControllerDataSource {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        return nil
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        return nil
    }
}
Клаус Йоргенсен
источник
0

pageViewController.view.isUserInteractionEnabled = false

gutte
источник
-1

Перевод ответа @ user2159978 на C #:

foreach (var view in pageViewController.View.Subviews){
   var subView = view as UIScrollView;
   if (subView != null){
     subView.ScrollEnabled = enabled;
   }
}
gtrujillos
источник
-1

(Swift 4) Вы можете удалить gestureRecognizers вашего pageViewController:

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

Если вы предпочитаете расширение:

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

и pageViewController.removeGestureRecognizers

Miguel
источник
-1

Объявите это так:

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

Тогда используйте это так:

scrollView?.isScrollEnabled = true //false
Бартломей Семанчик
источник
-1

Перечисление подпредставлений для поиска scrollView UIPageViewControllerне сработало для меня, так как я не могу найти scrollView в подклассе моего контроллера страницы. Итак, я решил отключить распознаватели жестов, но достаточно осторожно, чтобы не отключать необходимые.

Итак, я придумал это:

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

Поместите это внутрь, viewDidLoad()и все готово!

Гленн
источник
-1
 override func viewDidLayoutSubviews() {
    for View in self.view.subviews{
        if View.isKind(of: UIScrollView.self){
            let ScrollV = View as! UIScrollView
            ScrollV.isScrollEnabled = false
        }

    }
}

Добавьте это в свой класс pageviewcontroller. 100% рабочий

ap00724
источник
прокомментируйте, с какой проблемой вы столкнулись, этот код отлично работает: /
ap00724
-1

просто добавьте это свойство элемента управления в свой подкласс UIPageViewController :

var isScrollEnabled = true {
    didSet {
        for case let scrollView as UIScrollView in view.subviews {
            scrollView.isScrollEnabled = isScrollEnabled
        }
    }
}
Станислав Барановский
источник