在 ScrollView.frame 的部分禁用 UIScrollView 的滚动

Disable Scrolling of UIScrollView on part of the ScrollView.frame

你好希望每个人都安全。我有一个包含 UIScrollView(scrlView) 的 ViewController(vc)。 scrlView 包含另外两个 ViewControllers(vc1 & vc2)。在 vc1 上,我有一个按钮,按下它会向 vc1 添加一个子视图(subViewVc1)。为了不显示在委托开始拖动的其他scrlView页面上,我删除了subviewVc1。我遇到的问题是我无法停用 subViewVc1 框架所在的 scrlView 的滚动。

我尝试了多种方法,将滚动视图子类化为修改 touchesBegan,但 touchesBegan 可以识别触摸,如果用户执行即使是很小的拖动,手势也不再被识别。我试图添加一个滑动手势识别器,但我意识到它会干扰滚动视图手势。 有人知道该怎么做吗?

这是实现此目的的一种方法,您可以禁用特定视图上的交互,而不是禁用特定框架:

  1. 子类 UIView 您在点击按钮时添加的子类,没有真正的实现,只是为了识别 - 如果您愿意,也可以使用视图标签
  2. 子类 UIScrollView 并实现 touchesShouldCancel 以在与特定视图交互时取消滚动
  3. 设置scrollView.delaysContentTouches = false

实施

首先,我尝试重现您的情况,我创建了子视图控制器 class,带有一个将进入滚动视图的按钮:

class ChildVC: UIViewController
{
    let button = UIButton(type: .system)
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        view.backgroundColor = .yellow
        configureButton()
    }
    
    private func configureButton()
    {
        button.setTitle("Add View", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self,
                         action: #selector(didTapAddView),
                         for: .touchUpInside)
        view.addSubview(button)
        
        view.addConstraints([
        
            button.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            button.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            button.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            button.heightAnchor.constraint(equalToConstant: 50)
        
        ])
    }
    
    @objc
    private func didTapAddView()
    {
        let customView = UIView()
        customView.backgroundColor = .white
        customView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(customView)
        
        view.addConstraints([
        
            customView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
                                           constant: 16),
            customView.topAnchor.constraint(equalTo: view.topAnchor,
                                           constant: 16),
            customView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
                                           constant: -16),
            customView.bottomAnchor.constraint(equalTo: button.topAnchor,
                                           constant: -16)
        
        ])
    }
}

然后我用滚动视图创建了容器视图控制器,并将子 vc 嵌入到视图控制器中:

class ScrollTestViewController: UIViewController
{
    private let scrollView = UIScrollView()
    private let childVC1 = ChildVC()
    private let childVC2 = ChildVC()
    
    private let childVCHeight: CGFloat = 250
    private let childVCWidth: CGFloat = UIScreen.main.bounds.width
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        view.backgroundColor = .white
        title = "Scroll Test"
        configureScrollView()
        configureChildVC1()
        configureChildVC2()
    }
    
    override func viewDidLayoutSubviews()
    {
        super.viewDidLayoutSubviews()
        scrollView.contentSize = CGSize(width: childVC1.view.bounds.width * 2,
                                        height: scrollView.bounds.height)
    }
    
    private func configureScrollView()
    {
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
        
        view.addConstraints([
        
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        
        ])
    }
    
    private func configureChildVC1()
    {
        addChild(childVC1)
        childVC1.view.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(childVC1.view)
        
        view.addConstraints([
        
            childVC1.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            childVC1.view.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
            childVC1.view.widthAnchor.constraint(equalToConstant: childVCWidth),
            childVC1.view.heightAnchor.constraint(equalToConstant: childVCHeight)
        
        ])
    }
    
    private func configureChildVC2()
    {
        addChild(childVC2)
        childVC2.view.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(childVC2.view)
        
        view.addConstraints([
        
            childVC2.view.leadingAnchor.constraint(equalTo: childVC1.view.trailingAnchor),
            childVC2.view.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
            childVC2.view.widthAnchor.constraint(equalToConstant: childVCWidth),
            childVC2.view.heightAnchor.constraint(equalToConstant: childVCHeight)
        
        ])
    }
}

执行此操作后,您将得到以下结果:

没什么不同,即使在新添加的视图顶部滑动,用户也可以滚动到任何地方。

所以我做了这些改变:

  1. 创建一个自定义视图子class 这样我就可以识别我拖过哪个视图
class CustomView: UIView
{
    
}
  1. 创建自定义滚动视图以禁用特定视图的交互
fileprivate class CustomScrollView: UIScrollView
{
    override func touchesShouldCancel(in view: UIView) -> Bool
    {
        // Just for demo, I added this
        if view is CustomView
        {
            print("Scroll disabled, tapping custom view")
        }
        else
        {
            print("Scroll enabled")
        }
        
        // You only need this line
        return !(view is CustomView)
    }
}
  1. 使用滚动视图在容器视图控制器中进行更改
// Replace private let scrollView = UIScrollView() with
private let scrollView = CustomScrollView()

// Add this as well
scrollView.delaysContentTouches = false
  1. 更改子视图控制器以在点击按钮时使用自定义视图
@objc
private func didTapAddView()
{
    let customView = CustomView()
    customView.backgroundColor = .white
    customView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(customView)
    
    view.addConstraints([
    
        customView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
                                       constant: 16),
        customView.topAnchor.constraint(equalTo: view.topAnchor,
                                       constant: 16),
        customView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
                                       constant: -16),
        customView.bottomAnchor.constraint(equalTo: button.topAnchor,
                                       constant: -16)
    
    ])
}

现在,当您在视图上滑动时,它会阻止您滚动

根据 Mihai(OP)的评论更新

Do you have any idea how I could do it for a UIViewController instead of UIView, Im asking because the view is currently UIViewController and I lose some specifications about it in transforming it to an UIView.

So on the button press the view will add a UIViewController instead of a UIView

滚动视图可以将 UIView 添加到它的视图层次结构中,因此您只能检查您正在滚动的视图是否是特定类型的视图。

但是,如果您想在按钮点击而不是 UIView 上添加视图控制器,我仍然认为该解决方案可行。

  1. 保持自定义视图不变:
fileprivate class CustomView: UIView
{
    
}
  1. 创建视图控制器子class并将默认view更改为自定义视图子class
fileprivate class PurpleVC: UIViewController
{
    override func loadView()
    {
        super.loadView()

        // Change the default view from a UIView 
        // to be of type CustomView
        view = CustomView()

        view.backgroundColor = .purple
    }
}
  1. 在点击按钮而不是 UIView 时添加新的视图控制器
@objc
private func didTapAddView()
{
    let customVC = PurpleVC()
    customVC.view.translatesAutoresizingMaskIntoConstraints = false
    addChild(customVC)
    view.addSubview(customVC.view)
    
    view.addConstraints([
        
        customVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor,
                                               constant: 16),
        customVC.view.topAnchor.constraint(equalTo: view.topAnchor,
                                           constant: 16),
        customVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor,
                                                constant: -16),
        customVC.view.bottomAnchor.constraint(equalTo: button.topAnchor,
                                              constant: -16)
        
    ])
}
  1. 自定义滚动视图没有变化,因为它仍应检测作为视图控制器内主视图的自定义视图
fileprivate class CustomScrollView: UIScrollView
{
    override func touchesShouldCancel(in view: UIView) -> Bool
    {
        // Just for demo, I added this
        if view is CustomView
        {
            print("Scroll disabled, tapping custom view")
        }
        else
        {
            print("Scroll enabled")
        }
        
        // You only need this line
        return !(view is CustomView)
    }
}