如何禁用UIPageViewController的滑动手势?


125

在我的情况下,parent UIViewControllercontains UIPageViewController包含UINavigationControllerwhich包含UIViewController。我需要向最后一个视图控制器添加一个滑动手势,但是对滑动的处理就好像它们属于页面视图控制器一样。我尝试以编程方式和通过xib进行此操作,但没有结果。

因此,据我了解,只有UIPageViewController处理好手势,我才能实现自己的目标。如何解决这个问题?


7
使用pageViewControllerObject.view.userInteractionEnabled = NO;
Srikanth 2014年

6
错误的,因为它也阻止了所有内容。但我只需要阻止其手势
user2159978 2014年

你为什么做这个?您想要从应用程序中获得什么行为?听起来您正在使架构过于复杂。您能否解释一下您的目标行为。
Fogmeister 2014年

中的一页UIPageViewController包含UINavigationController。客户希望以滑动手势弹出导航控制器(如果可能的话),但是页面视图控制器却以滑动手势处理
user2159978 2014年

@Srikanth谢谢,这正是我所需要的!当我以编程方式触发页面更改并且有人用滑动中断了页面更改时,我的页面视图崩溃了,因此每次在代码中触发页面更改时,我都必须暂时禁用滑动...
Kuba Suder 2015年

Answers:


262

防止UIPageViewController滚动的记录方法是不分配dataSource属性。如果您分配数据源,它将进入“基于手势”的导航模式,这是您要防止的模式。

如果没有数据源,则当需要使用setViewControllers:direction:animated:completionmethod 时,您可以手动提供视图控制器,它会按需在视图控制器之间移动。

以上内容可以从Apple的UIPageViewController文档中得出(概述,第二段):

为了支持基于手势的导航,必须使用数据源对象提供视图控制器。


1
我也更喜欢这种解决方案。您可以将旧数据源保存在变量中,将数据源设置为nil,然后还原旧数据源以重新启用滚动。并不是很干净,但是比遍历基础视图层次结构找到滚动视图要好得多。
Matt R

3
响应文本字段子视图的键盘通知时,禁用数据源会导致问题。遍历视图的子视图以找到UIScrollView似乎更可靠。
AR Younce

9
这不是删除屏幕底部的点吗?而且,如果您没有滑动并且没有点,那么使用PageViewController根本没有多大意义。我猜提问者想保留点点滴滴。
Leo Flaherty 2015年

这有助于解决我在异步加载页面并且用户做出滑动手势时遇到的问题。布拉沃:)
杰克

1
A. R Younce是正确的。如果要响应键盘通知而禁用PageViewController的数据源(即,您不希望用户在键盘向上移动时水平滚动),则当您取消PageViewController的数据源时,键盘将立即消失。
micnguyen 2015年

82
for (UIScrollView *view in self.pageViewController.view.subviews) {

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

        view.scrollEnabled = NO;
    }
}

5
这保留了页面控件,这很好。
马库斯·亚当斯

1
这是一种很好的方法,因为它保留了页面控件(小点)。手动更改页面时,您将需要更新它们。我将这个答案用于这个stackoverflow.com/a/28356383/1071899
SimonBøgh,

1
为什么不for (UIView *view in self.pageViewController.view.subviews) {
呢?dengApro

请注意,通过点击左右两侧的页面控件,用户仍然可以在页面之间导航。
万事达卡

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

要变得更加优雅,您可以添加以下内容: var scrollView: UIScrollView? { for view in view.subviews { if let subView = view as? UIScrollView { return subView } } return nil }
Wagner Sales

此处的getter仅返回上一个子视图的启用状态。
安德鲁·邓肯

8

我已经为此奋斗了一段时间,并认为应该根据Jessedc的回答发布解决方案。删除PageViewController的数据源。

我将此添加到了PgeViewController类(链接到情节提要中的页面视图控制器,继承了UIPageViewControllerUIPageViewControllerDataSource):

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

编辑:此答案仅适用于页面卷曲样式。Jessedc的答案要好得多:无论样式如何,其工作方式都取决于记录在案的行为

UIPageViewController 公开其手势识别器数组,您可以用来禁用它们:

// myPageViewController is your UIPageViewController instance
for (UIGestureRecognizer *recognizer in myPageViewController.gestureRecognizers) {
    recognizer.enabled = NO;
}

8
您测试过代码了吗?myPageViewController.gestureRecognizers始终为空
user2159978 2014年

11
看来您的解决方案适用于页面卷曲样式,但是在我的应用中,我使用滚动视图样式
user2159978 2014年

8
不适用于水平滚动。返回的值始终是一个空数组。
庆阮2014年

你们为滚动样式弄清楚了吗?
Esqarrouth,2015年

4

如果你想要你的 UIPageViewController保持其滑动功能,同时允许内容控件使用其功能(滑动以删除等),只需canCancelContentTouches在中将其关闭即可UIPageViewController

把它放在你的 UIPageViewControllerviewDidLoadfunc。(迅速)

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

UIPageViewController有一个自动生成的子视图处理该手势。我们可以防止这些子视图取消内容手势。

从...

滑动以删除pageViewController内的tableView


谢谢卡特。我已经尝试过您的解决方案。但是,有时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
            }
        }
    }
}

3

我这样解决了(Swift 4.1)

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

3

对于SWIFTY方式@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的答案

斯威夫特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;
        }
    }
}

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 {
    // ...    
}

第三步:

将有效的滚动锁定方法添加到您的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")
    }
}

0

还有一个更简单的方法比大多数的答案在这里建议,这是返回nilviewControllerBeforeviewControllerAfter数据源的回调。

这会禁用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


-1

将@ user2159978的响应转换为C#:

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

-1

(快速4)您可以删除pageViewController的gestureRecognizers:

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

如果您喜欢扩展名:

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

pageViewController.removeGestureRecognizers


-1

像这样声明:

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

然后像这样使用它:

scrollView?.isScrollEnabled = true //false

-1

枚举子视图以查找a的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

-1

只需在您的UIPageViewController子类中添加此控件属性即可

var isScrollEnabled = true {
    didSet {
        for case let scrollView as UIScrollView in view.subviews {
            scrollView.isScrollEnabled = isScrollEnabled
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.