不使用transform捏合缩放UIView出现剧烈缩放偏移问题排查
问题分析与解决方案
你的代码核心问题出在对UIPinchGestureRecognizer的scale属性理解错误,以及没有正确记录初始缩放基准:
sender.scale是手势启动以来的总缩放比例(从1开始变化),不是每一次手势变化的增量比例。你每次用当前frame的宽高乘以这个scale,会导致缩放效果被重复累积——比如第一次scale到1.1,第二次到1.2,你的代码会计算成初始宽*1.1*1.2,而实际应该是初始宽*1.2,这就导致了轻微捏合就剧烈放大的问题。- 每次修改frame时先把origin设为(0,0)再重置center,逻辑冗余,还可能在复杂布局下引发位置计算偏差。
修正后的代码
首先在你的类中添加两个变量,用来保存手势启动时视图的初始尺寸:
private var initialViewWidth: CGFloat = 0 private var initialViewHeight: CGFloat = 0
然后修改捏合手势的处理方法:
@objc func didPinch(sender: UIPinchGestureRecognizer) { guard let subView = sender.view else { return } switch sender.state { case .began: // 手势启动时记录初始尺寸,作为缩放基准 initialViewWidth = subView.frame.width initialViewHeight = subView.frame.height case .changed: // 基于初始尺寸和当前总缩放比例计算目标尺寸 let targetWidth = initialViewWidth * sender.scale let targetHeight = initialViewHeight * sender.scale // 保持视图中心不变,计算新的frame let currentCenter = subView.center subView.frame = CGRect( x: currentCenter.x - targetWidth / 2, y: currentCenter.y - targetHeight / 2, width: targetWidth, height: targetHeight ) default: break } }
补充说明
- 这种方式通过直接修改frame来缩放,视图的
layer.borderWidth不会被缩放,完全符合你的需求。 - 如果想要实现以捏合手势的触摸点为中心缩放(而非视图自身中心),可以在
.began时记录触摸点相对于视图的位置,再在.changed时重新计算center,代码调整如下:
private var initialViewWidth: CGFloat = 0 private var initialViewHeight: CGFloat = 0 private var initialTouchPointInView: CGPoint = .zero @objc func didPinch(sender: UIPinchGestureRecognizer) { guard let subView = sender.view else { return } switch sender.state { case .began: initialViewWidth = subView.frame.width initialViewHeight = subView.frame.height // 记录触摸点在视图坐标系中的位置 initialTouchPointInView = sender.location(in: subView) case .changed: let targetWidth = initialViewWidth * sender.scale let targetHeight = initialViewHeight * sender.scale // 计算缩放后触摸点在父视图中的位置 let touchPointInSuperview = sender.location(in: subView.superview) // 计算新的center:触摸点位置减去缩放后触摸点在视图内的偏移量 let newCenterX = touchPointInSuperview.x - initialTouchPointInView.x * (targetWidth / initialViewWidth) let newCenterY = touchPointInSuperview.y - initialTouchPointInView.y * (targetHeight / initialViewHeight) subView.frame = CGRect( x: newCenterX - targetWidth / 2, y: newCenterY - targetHeight / 2, width: targetWidth, height: targetHeight ) default: break } }
内容的提问来源于stack exchange,提问作者XiOS
相关产品推荐
相关产品推荐

