CGAffineTransform缩放和平移 - 动画之前跳跃

32

我遇到了一个关于CGAffineTransform缩放和平移的问题,在动画块中设置一个视图的转换时,如果这个视图已经有了一个转换,那么在执行动画前,该视图会跳动一下。

例如:

// somewhere in view did load or during initialization
var view = UIView()
view.frame = CGRectMake(0,0,100,100)
var scale = CGAffineTransformMakeScale(0.8,0.8)
var translation = CGAffineTransformMakeTranslation(100,100)
var concat = CGAffineTransformConcat(translation, scale)
view.transform = transform

// called sometime later
func buttonPressed() {
    var secondScale = CGAffineTransformMakeScale(0.6,0.6)
    var secondTranslation = CGAffineTransformMakeTranslation(150,300)
    var secondConcat = CGAffineTransformConcat(secondTranslation, secondScale)
    UIView.animateWithDuration(0.5, animations: { () -> Void in 
         view.transform = secondConcat
    })

}

现在当调用buttonPressed()时,视图会在开始动画前向左上方跳动约10个像素。我只在使用连接转换时遇到了这个问题,仅使用平移转换可以正常工作。

编辑:由于我已经对此事进行了大量研究,所以我认为我应该提到,无论是否打开自动布局,都会出现此问题。


如果使用 CGAffineTransformTranslate(secondScale, 150, 300),会发生什么? - Ian MacDonald
结果完全相同 - matteok
“jumps a bit” 是什么意思?它会返回到标识变换吗?它是从跳跃动画到 secondConcat,还是先跳跃,返回到 concat,然后再动画到 secondConcat - Logan Moseley
视图从(100,100)开始(第一个变换的位置),当调用buttonPressed()时,视图会从(100,100)跳转到大约(85,85),然后动画到第二个位置/比例。 - matteok
2
你解决过这个问题吗?我现在正在调试同样的问题。 - Sami Samhuri
1
很不幸,我没有这样做。最后,我将一个视图包裹在另一个视图中,这样我就可以翻译外部视图并缩放内部视图。通过这种方式,我成功地解决了这个问题。 - matteok
5个回答

54
我遇到了相同的问题,但找不到确切的问题来源。跳跃似乎只在非常特定的条件下出现:如果视图从变换t1动画到变换t2,并且两个变换都是比例和平移的组合(这正是您的情况)。考虑到以下解决方法,它对我来说毫无意义,我认为这是核心动画中的一个错误。

首先,我尝试使用CATransform3D而不是CGAffineTransform

旧代码:

var transform = CGAffineTransformIdentity
transform = CGAffineTransformScale(transform, 1.1, 1.1)
transform = CGAffineTransformTranslate(transform, 10, 10)
view.layer.setAffineTransform(transform)

新代码:

var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform
新代码应该与旧代码等效(第四个参数设置为1.00,因此在z方向上没有缩放/平移),实际上它显示相同的跳跃。然而,黑魔法来了:在比例变换中,将z参数更改为与1.0不同的任何值,像这样:
transform = CATransform3DScale(transform, 1.1, 1.1, 1.01)

这个参数本应该没有任何影响,但现在跳跃已经消失了。


4
我认为这个回答没有得到足够的赞誉...我已经连续几天在编码,但无法修复这个问题。 - farzadshbfn
2
☝️同样的问题我也遇到过,只是在简单的缩放和平移中。在动画之前,图层会向左“移动”一点点。将z轴应用1.01的比例尺修复了这个问题。 - Chris
1
针对任何好奇的人,经过我的测试,这个问题已经在iOS 13中得到解决。 - Tomer Shemesh
我的模拟器运行的是iOS 13,仍然存在这个问题。 - Krøllebølle

4
看起来像是苹果UIView动画内部的bug。当苹果在两个值之间插值CGAffineTransform变化以创建动画时,它应该执行以下步骤:
  • 提取平移、缩放和旋转
  • 在开始和结束之间插入提取的值
  • 为每个插值步骤组装CGAffineTransform

组装应按以下顺序进行:

  • 平移
  • 缩放
  • 旋转

但看起来苹果在缩放和旋转之后才进行平移。这个bug应该由苹果修复。


1
我不知道为什么,但这段代码可以工作。
更新:
我成功地将缩放、平移和旋转组合在一起,从任何变换状态到任何新的变换状态。
我认为动画开始时重新解释了变换。
起始变换的锚点在新变换中被考虑,然后我们将其转换为旧变换。
self.v  = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
self.v?.backgroundColor = .blue
self.view.addSubview(v!)

func buttonPressed() {
    let view = self.v!

    let m1 = view.transform
    let tempScale = CGFloat(arc4random()%10)/10 + 1.0
    let tempRotae:CGFloat = 1
    let m2 = m1.translatedBy(x: CGFloat(arc4random()%30), y: CGFloat(arc4random()%30)).scaledBy(x: tempScale, y: tempScale).rotated(by:tempRotae)
    self.animationViewToNewTransform(view: view, newTranform: m2)
}    


func animationViewToNewTransform(view: UIView, newTranform: CGAffineTransform) {
    // 1. pointInView.apply(view.transform) is not correct point.
    // the real matrix is mAnchorToOrigin.inverted().concatenating(m1).concatenating(mAnchorToOrigin)
    // 2. animation begin trasform is relative to final transform in final transform coordinate

    // anchor and mAnchor
    let normalizedAnchor0 = view.layer.anchorPoint
    let anchor0 = CGPoint(x: normalizedAnchor0.x * view.bounds.width, y: normalizedAnchor0.y * view.bounds.height)
    let mAnchor0 = CGAffineTransform.identity.translatedBy(x: anchor0.x, y: anchor0.y)

    // 0->1->2
    //let origin = CGPoint(x: 0, y: 0)
    //let m0 = CGAffineTransform.identity
    let m1 = view.transform
    let m2 = newTranform

    // rotate and scale relative to anchor, not to origin
    let matrix1 = mAnchor0.inverted().concatenating(m1).concatenating(mAnchor0)
    let matrix2 = mAnchor0.inverted().concatenating(m2).concatenating(mAnchor0)
    let anchor1 = anchor0.applying(matrix1)
    let mAnchor1 = CGAffineTransform.identity.translatedBy(x: anchor1.x, y: anchor1.y)
    let anchor2 = anchor0.applying(matrix2)
    let txty2 = CGPoint(x: anchor2.x - anchor0.x, y: anchor2.y - anchor0.y)
    let txty2plusAnchor2 = CGPoint(x: txty2.x + anchor2.x, y: txty2.y + anchor2.y)
    let anchor1InM2System = anchor1.applying(matrix2.inverted()).applying(mAnchor0.inverted())
    let txty2ToM0System = txty2plusAnchor2.applying(matrix2.inverted()).applying(mAnchor0.inverted())
    let txty2ToM1System = txty2ToM0System.applying(mAnchor0).applying(matrix1).applying(mAnchor1.inverted())

    var m1New = m1
    m1New.tx = txty2ToM1System.x + anchor1InM2System.x
    m1New.ty = txty2ToM1System.y + anchor1InM2System.y

    view.transform = m1New
    UIView.animate(withDuration: 1.4) {
        view.transform = m2
    }
}

我也尝试了zScale解决方案,如果在第一个变换或每个变换中设置zScale非1,则似乎也可以工作。
    let oldTransform = view.layer.transform
    let tempScale = CGFloat(arc4random()%10)/10 + 1.0
    var newTransform = CATransform3DScale(oldTransform, tempScale, tempScale, 1.01)
    newTransform = CATransform3DTranslate(newTransform, CGFloat(arc4random()%30), CGFloat(arc4random()%30), 0)
    newTransform = CATransform3DRotate(newTransform, 1, 0, 0, 1)

    UIView.animate(withDuration: 1.4) {
        view.layer.transform = newTransform
    }

0
问题的根源是变换缺乏透视信息。
您可以通过修改3D变换的m34属性来添加透视信息。
var transform = CATransform3DIdentity
transform.m34 = 1.0 / 200 //your own perspective value here
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform

请您能否再详细解释一下,并且说明为什么这个“神奇”的方法会起作用?链接 - meaning-matters

0

不要使用CGAffineTransformMakeScale()和CGAffineTransformMakeTranslation(),它们会基于CGAffineTransformIdentity(即无变换)创建一个变换。相反,你应该使用CGAffineTransformScale()和CGAffineTransformTranslate(),它们会基于视图的当前变换进行缩放和平移操作,从而在现有变换的基础上进行。


我需要在动画期间设置绝对变换,这意味着我不想添加到现有的变换中,而是替换它进行动画。 - matteok
1
我刚刚尝试使用CGAffineTransformTranslate而不是CGAffineTransformMakeTranslate,但问题仍然存在。 - matteok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接