You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))
}

问题根源

  1. 坐标系统未适配旋转状态:当前缩放计算基于视图未旋转的原始坐标,当视图倒置(旋转π弧度)时,触摸位置相对视图中心的方向被翻转,导致距离变化量的符号反向,最终缩放方向颠倒。
  2. 直接修改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
    }
}

方案说明

  1. 统一变换管理:用CGAffineTransform同时处理旋转和缩放,避免直接修改bounds引发的布局冲突,解决闪烁问题。
  2. 角度修正适配旋转:通过计算触摸向量的角度差,并结合视图当前旋转角度修正,确保无论视图处于任何旋转状态,径向缩放方向(远离中心放大、靠近中心缩小)始终正确。
  3. 比例式缩放计算:使用距离比例替代绝对距离变化计算缩放,避免视图尺寸变化导致的计算偏差,提升逻辑稳定性。

内容的提问来源于stack exchange,提问作者user20306199

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 23:21:06