CODE & ZEN

凯旋的博客

我是孙凯旋 @skx926
一名来自中国的 iOS 开发者
写过 Android
也会点 Web
深知付出更多才能收获更多
唯有在技术的道路上勤勤恳恳
方得片刻安心


利用Pinch和Pan手势实现UIView的缩放和拖拽

先来看下我们要实现的效果:

Demo
Demo

其实这个效果UIScrollView已经自带了,但是某些情况下由于某些原因我们可能不能或者不想使用UIScrollView,那么我们就得自己实现这个效果。

如果要单独用Pinch手势实现缩放或者用Pan手势实现拖拽,这个比较简单,直接贴代码:

// 利用Pinch手势实现缩放
func didPinch(_ pinch: UIPinchGestureRecognizer) {
if pinch.state != .changed {
return
}
let scale = pinch.scale
let scaleTransform = imageView.transform.scaledBy(x: scale, y: scale)
imageView.transform = scaleTransform
pinch.scale = 1
}
// 利用Pan手势实现拖拽
func didPan(_ pan: UIPanGestureRecognizer) {
if pan.state != .changed {
return
}
let scale = imageView.frame.size.width / initialFrame.size.width
let translation = pan.translation(in: view)
let transform = imageView.transform.translatedBy(x: translation.x / scale, y: translation.y / scale)
imageView.transform = transform
pan.setTranslation(.zero, in: view)
}

确实是很简单,但是从不同位置缩放一下图片你就会发现不论你的手指在什么位置,图片总是以中心为基准进行放大缩小,感觉很不自然。这是为什么呢?因为我们上面的代码只是单纯根据手势的scale对图片进行了缩放,根本没有考虑到手指的位置。要有良好的缩放体验,一般来说都需要以两指的中心点为基准来进行缩放。这样的话我们就需要在缩放的过程中对图片再增加一个偏移,那么这个偏移的值具体是多少呢?我们来看一张图:

如上图所示,假设我们有一个初始状态的View为A,他的中心点为a,我们以点b为基准把A放大一倍,理想的结果就是C,缩放的基准点b点不会发生位移。

我们可以把这个过程拆分成两个步骤: 1. 把A放大一倍至B 2. 给B添加偏移 (x, y) 至C

那么偏移(x, y)怎么求得呢?

根据上图我们可以计算出基准点b相对于中心点a的偏移(dx, dy):

// location为Pinch手势的中心点b
let dx = imageView.frame.midX - location.x
let dy = imageView.frame.midY - location.y

然后计算出放大之后的基准点b相对于中心点a的偏移(sdx, sdy):

// scale为缩放的比例
let sdx = dx * scale
let sdy = dy * scale

由图可得(x, y)为上面的两项的差:

let x = sdx - dx
let y = sdy - dy

最终的代码如下:

func didPinch(_ pinch: UIPinchGestureRecognizer) {
if pinch.state != .changed {
return
}
let scale = pinch.scale
let location = pinch.location(in: view)
let scaleTransform = imageView.transform.scaledBy(x: scale, y: scale)
imageView.transform = scaleTransform

let dx = imageView.frame.midX - location.x
let dy = imageView.frame.midY - location.y
let x = dx * scale - dx
let y = dy * scale - dy

// 这样的话计算会有错误
//imageView.transform = imageView.transform.translatedBy(x: x, y: y);

let translationTransform = CGAffineTransform(translationX: x, y: y)
imageView.transform = imageView.transform.concatenating(translationTransform)

pinch.scale = 1
}

这里有个比较坑的地方就是给imageView应用完ScaleTransform之后直接使用public func translatedBy(x tx: CGFloat, y ty: CGFloat) -> CGAffineTransform方法增加偏移的时候的结果与我们预期的不一样。

比如说我们的imageView初始状态的frame是(x: 100, y: 100, width: 100, height: 100), 缩放的时候以左上角点b为基准,使用ScaleTransform放大一倍之后变为(x: 0, y: 0, width: 200, height: 200),然后我们通过translatedBy方法偏移之后得到的frame为(x: 200, y: 200, width: 200, height: 200), 这个结果与我们所期望的(x: 100, y: 100, width: 200, height: 200)有所不同。而使用concatenating方法就不会有问题,按照我的理解,这两个方法应该产生同样的结果才对。如果你知道其中的原因,欢迎在下面留言。

文章里提到的内容我已经写了一个Demo欢迎点击此处进行下载查看。

最近的文章

解决Storyboard中设置的颜色和代码设置的颜色不一样的问题

最近发现在Storyboard中设置的颜色和用代码设置的颜色居然不一样,当时我就震惊了! 更让人震惊的是搞了这么多年iOS开发,居然现在才发现!惭愧啊。。。 网上查了一下才知道原来Xcode里的色彩选择器默认的Color Profile是Generic RGB,如下图所示: 把它改为Device …

于  iOS开发 继续阅读
更早的文章

获取App Store下载的Xcode安装包

通过 App Store 下载安装 Xcode 的时候可能会遇到各种奇葩的事情。 这不,我刚才就摊上事了:用公司的渣网络辛辛苦苦下载两个多小时眼看开始安装了结果弹框提示我说空间不足无法继续安装。既然空间不足咱就清理一下呗,清理完了之后想要继续安装发现没有办法重新开始安装,App Store 里的按钮 …

于  Mac技巧 继续阅读
comments powered by Disqus