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
}
}
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
})
}
}
}
2条答案
按热度按时间yshpjwxd1#
正如我在评论中提到的,有几种方法可以实现这一点......使用哪种方法取决于您还需要对其他UI元素做什么。
因此,如果没有任何额外的信息,这里有两种方法来解决这个问题。
让我们从一个“Base”视图控制器类开始:
它自己运行时看起来像这样:
一个绿色背景的滚动视图(所以我们可以看到它)...滚动内容是一个垂直堆栈视图,有12个标签(所以我们有东西可以滚动)。
到目前为止,还没有什么新的东西。你可以滚动滚动视图,但只能在里面拖动。
第一种方法是创建一个“基本”控制器的子类,但我们将向控制器的视图添加一个
UIPanGestureRecognizer
:现在,拖动滚动视图 * 外部 * 的任意位置将设置滚动视图内容的内容偏移,因此看起来像是在滚动视图内部拖动。
请注意,如果我们将鼠标拖过顶部或底部,本例中的内容会“反弹”,但由于您没有说明任何目标,因此它不会模拟滚动视图的减速。您可以通过计算手势的速度、幅度等来实现这一点。
第二种方法是子类化
UIScrollView
并覆盖hitTest(...)
:因此,此控制器(再次将“基本”控制器子类化)使用
HTScrollView
代替默认的UIScrollView
:同样,拖动任何地方 * 外 * 滚动视图将看起来就像你是在它里面拖动...与获得内置的减速和反弹的额外优势。
这里的缺点是,您将无法与滚动视图本身中的任何子视图交互-例如按钮。
为了克服这个问题,我们需要使用一个更完整的
hitTest(...)
覆盖,它将遍历滚动视图的子视图,并适当地返回视图/控件。ql3eal8s2#
我上面提到的WWDC视频是2014年的“高级滚动视图和触摸处理技术”。出于某种原因,苹果已经删除了该部分。
https://www.wwdcnotes.com/notes/wwdc14/235/
这方面的要点是:
他们正在将滚动视图的平移手势添加到另一个视图中。