UIView倒置后单指缩放方向异常及闪烁问题求助
单指旋转缩放UIView时倒置后方向反转及闪烁问题解决
问题场景
使用UIPanGestureRecognizer实现单指旋转缩放内部包含UIImageView的UIView,常规状态下旋转和缩放功能正常,但视图倒置后:
- 缩放方向反向(放大操作变成缩小,缩小操作变成放大)
- 在角落旋转时出现闪烁、功能异常
当前实现代码
手势处理方法
@objc func ScaleGest(gesture: UIPanGestureRecognizer) { guard let gestureSuperView = gesture.view?.superview else { return } let center = frameScaleBut.center let touchLocation = gesture.location(in: self.view) if gesture.state == UIPanGestureRecognizer.State.began { self.initialDistance = distance(center, touchLocation) } else if gesture.state == UIPanGestureRecognizer.State.changed { let dist = distance(center, touchLocation) - initialDistance let smallScale: CGFloat = editingMode == .sticker ? 90 : 190 let nextScale: CGSize = CGSize(width: gestureSuperView.bounds.width + dist, height: gestureSuperView.bounds.height + dist) if (nextScale.width >= (self.view.frame.width * 0.8)) || nextScale.width <= smallScale { return } gestureSuperView.bounds.size = CGSize(width: gestureSuperView.bounds.width + dist, height: gestureSuperView.bounds.height + dist) } }
距离计算方法
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat { let xDist = a.x - b.x let yDist = a.y - b.y return CGFloat(sqrt(xDist * xDist + yDist * yDist)) }
问题根源
- 坐标系统未适配旋转状态:当前缩放计算基于视图未旋转的原始坐标,当视图倒置(旋转π弧度)时,触摸位置相对视图中心的方向被翻转,导致距离变化量的符号反向,最终缩放方向颠倒。
- 直接修改bounds的冲突:旋转状态下直接修改
bounds.size,会导致视图的布局变换和旋转矩阵产生计算冲突,引发闪烁和异常。
解决方案
改用CGAffineTransform统一管理旋转和缩放,结合触摸向量的角度修正,适配任意旋转状态下的缩放逻辑。
步骤1:添加必要属性
在类中添加用于保存初始状态的属性:
private var initialTransform: CGAffineTransform = .identity private var initialScale: CGFloat = 1.0 private var initialTouchAngle: CGFloat = 0.0
步骤2:扩展CGAffineTransform获取缩放和旋转值
extension CGAffineTransform { // 获取当前变换的缩放比例 var scale: CGFloat { return sqrt(a * a + c * c) } // 获取当前变换的旋转角度 var rotation: CGFloat { return atan2(b, a) } }
步骤3:修改手势处理逻辑
@objc func scaleGestureHandler(gesture: UIPanGestureRecognizer) { guard let targetView = gesture.view?.superview else { return } let viewCenter = targetView.center let touchLocation = gesture.location(in: self.view) switch gesture.state { case .began: // 保存初始变换状态和缩放比例 initialTransform = targetView.transform initialScale = initialTransform.scale // 计算初始触摸点相对视图中心的角度 let touchVector = CGPoint(x: touchLocation.x - viewCenter.x, y: touchLocation.y - viewCenter.y) initialTouchAngle = atan2(touchVector.y, touchVector.x) case .changed: // 计算当前触摸点相对视图中心的向量和角度 let currentTouchVector = CGPoint(x: touchLocation.x - viewCenter.x, y: touchLocation.y - viewCenter.y) let currentTouchAngle = atan2(currentTouchVector.y, currentTouchVector.x) // 结合视图当前旋转角度,修正触摸角度差 let viewRotation = initialTransform.rotation let adjustedAngle = currentTouchAngle - viewRotation - initialTouchAngle // 通过余弦值判断径向缩放方向,计算缩放比例 let baseDistance = distance(viewCenter, CGPoint(x: viewCenter.x + cos(initialTouchAngle), y: viewCenter.y + sin(initialTouchAngle))) let distanceRatio = cos(adjustedAngle) * distance(viewCenter, touchLocation) / baseDistance // 转换为目标缩放比例并限制范围 let originalViewWidth = targetView.bounds.width let smallScale: CGFloat = editingMode == .sticker ? 90 / originalViewWidth : 190 / originalViewWidth let maxScale: CGFloat = (self.view.frame.width * 0.8) / originalViewWidth var newScale = initialScale * distanceRatio // 限制缩放范围,防止超出边界 newScale = max(min(newScale, maxScale), smallScale) // 应用变换:保持原有旋转,更新缩放比例 targetView.transform = initialTransform.scaledBy(x: newScale / initialScale, y: newScale / initialScale) default: break } }
方案说明
- 统一变换管理:用
CGAffineTransform同时处理旋转和缩放,避免直接修改bounds引发的布局冲突,解决闪烁问题。 - 角度修正适配旋转:通过计算触摸向量的角度差,并结合视图当前旋转角度修正,确保无论视图处于任何旋转状态,径向缩放方向(远离中心放大、靠近中心缩小)始终正确。
- 比例式缩放计算:使用距离比例替代绝对距离变化计算缩放,避免视图尺寸变化导致的计算偏差,提升逻辑稳定性。
内容的提问来源于stack exchange,提问作者user20306199
相关产品推荐
相关产品推荐

