CGAffineTransform缩放UIView 二次缩放跳回初始帧问题
问题:拖拽角点缩放UIView二次触发时视图复位
现象
通过拖拽视图角点触发平移手势,调用CGAffineTransform的scale方法调整带子视图的父UIView尺寸时,首次缩放效果符合预期,再次触发缩放时,视图会直接跳回初始frame状态,仿佛之前的缩放操作从未执行。
现有实现代码
- 手势began阶段获取初始状态
if gesture.state == .began { // 从首次触摸获取初始值 initialFrame = self.frame; touchStart = gesture.location(in: superview) }
- 右上角控制点拖拽缩放逻辑
case topRight: if gesture.state == .began { self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 1)) } let deltaX = -1 * (touchStart.x - gesture.location(in: superview).x) let deltaY = 1 * (touchStart.y - gesture.location(in: superview).y) let newWidth = initialFrame.width + deltaX; let newHeight = initialFrame.height + deltaY; let scaleX:CGFloat = newWidth / initialFrame.width; let scaleY:CGFloat = newHeight / initialFrame.height; self.transform = CGAffineTransform.identity.scaledBy(x: scaleX, y: scaleY)
- 手势ended阶段重置锚点
if gesture.state == .ended { self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5)) }
操作演示效果如下:
问题根因
- 核心错误:UIView设置非
identity的transform后,frame属性值是变换后的外层投影值,不能作为缩放计算的基准。CGAffineTransform的缩放是作用在视图原始bounds尺寸上的,第二次触发手势时取到的initialFrame是上一次缩放后的投影尺寸,用这个值计算缩放比例,再把变换直接应用在CGAffineTransform.identity上,相当于永远以视图最初始的bounds为基准做缩放,算出来的比例和预期完全不符,就会出现跳回初始尺寸的现象。 - 潜在问题:自定义
setAnchorPoint方法如果没有同步调整layer的position做位置补偿,修改锚点时会直接导致视图位置偏移,也会加剧跳转问题。
修复方案
- 替换手势began阶段的初始值取值逻辑,不要存
initialFrame,改为存储手势触发时的初始bounds尺寸、当前已生效的transform缩放值、初始触摸点。bounds是视图自身坐标系下的尺寸,不会受transform影响,永远可靠。 - 缩放计算时要叠加上一次的缩放结果,不要每次都从
CGAffineTransform.identity开始生成新变换。如果想沿用现有基于frame增量的计算逻辑,可以在手势began时先把当前transform的效果固化:将transform设为identity,同时把frame更新为当前视觉上的尺寸位置,后续计算逻辑就可以正常运行。 - 使用带位置补偿的锚点设置方法,避免修改锚点时视图位置跳转,正确实现如下:
extension UIView { func setAnchorPoint(anchorPoint: CGPoint) { var newPoint = CGPoint(x: bounds.width * anchorPoint.x, y: bounds.height * anchorPoint.y) var oldPoint = CGPoint(x: bounds.width * layer.anchorPoint.x, y: bounds.height * layer.anchorPoint.y) newPoint = newPoint.applying(transform) oldPoint = oldPoint.applying(transform) var position = layer.position position.x += newPoint.x - oldPoint.x position.y += newPoint.y - oldPoint.y layer.position = position layer.anchorPoint = anchorPoint } }
- 手势结束重置锚点时,直接调用上述带补偿的方法即可,不需要额外做位置修正。
内容的提问来源于stack exchange,提问作者Joaquin Pereira
相关产品推荐
相关产品推荐

