问题
做过iOS开发的都知道,UIScrollView是个好东西。UIView丢进去,即可自由拖拽、放大缩小。
但UIScrollView有个很讨厌的特性,双指放大时,画布也会随着两根手指拖动,无法固定放大中心点。
尝试
猜想,放大缩小的过程中,PanGesture拖动手势同时响应,造成画布放大同时可拖动的问题。
直接在放大开始前禁用pan手势。
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
panGestureRecognizer.isEnabled = false
}
可惜并没有什么卵用。实际上,pinch手势同时处理了画布的拖动和放大。
再一次尝试
如果在zoom的时候,手动控制contentOffset变化,将拖拽操作改变的contentOffset消除掉,是不是就能解决问题了呢?
只需要在开始放大时,记录下当前contentOffset大小,然后在scrollViewDidZoom中强行令它保持不变就可以了。
func scrollViewDidZoom(_ scrollView: UIScrollView) {
contentOffset = pinchStartContentOffset
}
var pinchStartContentOffset: CGPoint = .zero
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
pinchStartContentOffset = contentOffset
}
现在,双指无法对UIScrollView造成拖动了。但UIScrollView会以contentView的左上角为中心放大缩小,整个图片往右下方跑,失去了以双指中点为中心放大的自然感。
思考
如果要满足以双指为中心缩放,只需修正contentOffset即可。
如图所示,UIScrollView的大小和屏幕大小相等,contentView小矩形希望以粉色点为中心点(中心点在放大前后,在屏幕上的位置不变),放大至紫色大矩形的大小。
前面观察到,当contentOffset不变时,UIScrollView默认的行为,是以contentView的左上角为原点缩放。缩放后,粉色中心点需要向左上↖️移动“小橙”的距离,即可满足中心点不动的要求。
解决
func scrollViewDidZoom(_ scrollView: UIScrollView) {
guard pinchCenter != .zero else { return }
contentOffset.x = pinchStartContentOffset.x + (pinchCenter.x + pinchStartContentOffset.x) * (zoomScale / pinchStartScale - 1)
contentOffset.y = pinchStartContentOffset.y + (pinchCenter.y + pinchStartContentOffset.y) * (zoomScale / pinchStartScale - 1)
pinchStartScale = zoomScale
pinchStartContentOffset = contentOffset
}
var pinchCenter: CGPoint = .zero
var pinchStartScale: CGFloat = 0
var pinchStartContentOffset: CGPoint = .zero
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
guard pinchGestureRecognizer?.numberOfTouches ?? 0 >= 2 else { return }
guard
let p1 = pinchGestureRecognizer?.location(ofTouch: 0, in: self),
let p2 = pinchGestureRecognizer?.location(ofTouch: 1, in: self)
else { return }
pinchCenter = CGPoint(x: ((p1.x + p2.x) / 2 - contentOffset.x),
y: ((p1.y + p2.y) / 2 - contentOffset.y))
pinchStartScale = zoomScale
pinchStartContentOffset = contentOffset
}
要注意的是,在使用UIScrollView的zoom(to:,等自动调整内容位置的方法时,pinchCenter必须手动设为zero。