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

CGAffineTransform缩放UIView 二次缩放跳回初始帧问题

问题:拖拽角点缩放UIView二次触发时视图复位

现象

通过拖拽视图角点触发平移手势,调用CGAffineTransform的scale方法调整带子视图的父UIView尺寸时,首次缩放效果符合预期,再次触发缩放时,视图会直接跳回初始frame状态,仿佛之前的缩放操作从未执行。

现有实现代码

  1. 手势began阶段获取初始状态
if gesture.state == .began {
    // 从首次触摸获取初始值
    initialFrame = self.frame;
    touchStart = gesture.location(in: superview)
}
  1. 右上角控制点拖拽缩放逻辑
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)
  1. 手势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做位置补偿,修改锚点时会直接导致视图位置偏移,也会加剧跳转问题。

修复方案

  1. 替换手势began阶段的初始值取值逻辑,不要存initialFrame,改为存储手势触发时的初始bounds尺寸、当前已生效的transform缩放值、初始触摸点。bounds是视图自身坐标系下的尺寸,不会受transform影响,永远可靠。
  2. 缩放计算时要叠加上一次的缩放结果,不要每次都从CGAffineTransform.identity开始生成新变换。如果想沿用现有基于frame增量的计算逻辑,可以在手势began时先把当前transform的效果固化:将transform设为identity,同时把frame更新为当前视觉上的尺寸位置,后续计算逻辑就可以正常运行。
  3. 使用带位置补偿的锚点设置方法,避免修改锚点时视图位置跳转,正确实现如下:
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
    }
}
  1. 手势结束重置锚点时,直接调用上述带补偿的方法即可,不需要额外做位置修正。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 22:24:13