swift 从外部UIPanGestureRecognizer滚动UIScrollView

nqwrtyyt  于 2023-02-28  发布在  Swift
关注(0)|答案(2)|浏览(183)

设置

因此,我在视图的顶部有一个UIScrollView,UIPanGestureRecognizer覆盖了整个视图。

目标

当用户通过触摸进行平移时,我希望UIScrollView以滚动视图中的平移手势拾取触摸的方式滚动。

注解

几年前,在一个WWDC会议视频中,苹果向我们展示了如何使用下拉手势在主屏幕上进行搜索。他们展示了下拉平移手势实际上是如何被搜索结果面板中的滚动视图拾取的。不知何故,现在我找不到这个视频了。

yshpjwxd

yshpjwxd1#

正如我在评论中提到的,有几种方法可以实现这一点......使用哪种方法取决于您还需要对其他UI元素做什么。
因此,如果没有任何额外的信息,这里有两种方法来解决这个问题。
让我们从一个“Base”视图控制器类开始:

class ScrollTestBaseVC: UIViewController {
    
    var scrollView: UIScrollView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // scrollView will be set by the subclass
        //  if it is not, that means we are running THIS view controller
        if scrollView == nil {
            scrollView = UIScrollView()
        }
        
        // vertical stack view to use as the scroll content
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = 40.0
        
        // 12 labels in the stack view
        for i in 1...12 {
            let v = UILabel()
            v.backgroundColor = .yellow
            v.text = "Label \(i)"
            stackView.addArrangedSubview(v)
        }
        
        // add stack view to scroll view
        stackView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(stackView)
        
        // add scroll view to view
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
        
        let g = view.safeAreaLayoutGuide
        let cg = scrollView.contentLayoutGuide
        let fg = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            scrollView.heightAnchor.constraint(equalToConstant: 240.0),
            
            stackView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 8.0),
            stackView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 8.0),
            stackView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -8.0),
            stackView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -8.0),
            
            stackView.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: -16.0),
            
        ])
        
        // so we can see framing
        stackView.backgroundColor = .systemBlue
        scrollView.backgroundColor = .systemGreen
        
    }
    
}

它自己运行时看起来像这样:

一个绿色背景的滚动视图(所以我们可以看到它)...滚动内容是一个垂直堆栈视图,有12个标签(所以我们有东西可以滚动)。
到目前为止,还没有什么新的东西。你可以滚动滚动视图,但只能在里面拖动。
第一种方法是创建一个“基本”控制器的子类,但我们将向控制器的视图添加一个UIPanGestureRecognizer

class PanScrollVC: ScrollTestBaseVC {
    
    // we use this to track the start of the pan gesture
    var currY: CGFloat = 0
    
    override func viewDidLoad() {
        
        // use a default UIScrollView
        scrollView = UIScrollView()
        
        super.viewDidLoad()
        
        // add pan gesture to view
        let p = UIPanGestureRecognizer(target: self, action: #selector(panHandler(_:)))
        view.addGestureRecognizer(p)
        
    }
    
    @objc func panHandler(_ gesture: UIPanGestureRecognizer) {
        
        let translation = gesture.translation(in: view)
        
        if gesture.state == .began {
            
            // save current scroll Y offset
            currY = scrollView.contentOffset.y
            
        } else if gesture.state == .changed {
            
            // move scroll view content while dragging
            scrollView.contentOffset.y = currY - translation.y
            
        } else {
            
            // finished dragging
            
            // this will "bounce" the content if we've
            //  dragged past the top or bottom
            //
            // if you want to mimic a scroll view's deceleration
            //  you'd need to implement the gesture's velocity, magnitude, etc
            
            let mxY: CGFloat = scrollView.contentSize.height - scrollView.frame.height
            let finalY = min(mxY, max(scrollView.contentOffset.y, 0.0))
            
            UIView.animate(withDuration: 0.3, animations: {
                self.scrollView.contentOffset.y = finalY
            })
            
        }
        
    }
    
}

现在,拖动滚动视图 * 外部 * 的任意位置将设置滚动视图内容的内容偏移,因此看起来像是在滚动视图内部拖动。
请注意,如果我们将鼠标拖过顶部或底部,本例中的内容会“反弹”,但由于您没有说明任何目标,因此它不会模拟滚动视图的减速。您可以通过计算手势的速度、幅度等来实现这一点。
第二种方法是子类化UIScrollView并覆盖hitTest(...)

class HTScrollView: UIScrollView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        return self
    }
}

因此,此控制器(再次将“基本”控制器子类化)使用HTScrollView代替默认的UIScrollView

class CustomScrollVC: ScrollTestBaseVC {
    
    override func viewDidLoad() {
        
        scrollView = HTScrollView()
        
        super.viewDidLoad()
        
    }
    
}

同样,拖动任何地方 * 外 * 滚动视图将看起来就像你是在它里面拖动...与获得内置的减速和反弹的额外优势。
这里的缺点是,您将无法与滚动视图本身中的任何子视图交互-例如按钮。
为了克服这个问题,我们需要使用一个更完整的hitTest(...)覆盖,它将遍历滚动视图的子视图,并适当地返回视图/控件。

ql3eal8s

ql3eal8s2#

我上面提到的WWDC视频是2014年的“高级滚动视图和触摸处理技术”。出于某种原因,苹果已经删除了该部分。
https://www.wwdcnotes.com/notes/wwdc14/235/
这方面的要点是:

self.view.addGestureRecognizer(myScrollView.panGestureRecognizer)

他们正在将滚动视图的平移手势添加到另一个视图中。

相关问题