xcode CGAffineTransform 缩放和平移 - 在动画之前跳转

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27931421/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 06:28:55  来源:igfitidea点击:

CGAffineTransform scale and translation - jump before animation

iosxcodescaletransformationcgaffinetransform

提问by matteok

I am struggling with an issue regarding CGAffineTransform scale and translation where when I set a transform in an animation block on a view that already has a transform the view jumps a bit before animating.

我正在努力解决有关 CGAffineTransform 缩放和平移的问题,当我在已经具有变换的视图上的动画块中设置变换时,视图在动画之前会跳跃一点。

Example:

例子:

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

}

Now when buttonPressed() is called the view jumps to the top left about 10 pixels before starting to animate. I only witnessed this issue with a concat transform, using only a translation transform works fine.

现在当 buttonPressed() 被调用时,视图会在开始动画之前跳转到左上角大约 10 个像素。我只用 concat 变换目睹了这个问题,只使用平移变换工作正常。

Edit: Since I've done a lot of research regarding the matter I think I should mention that this issue appears regardless of whether or not auto layout is turned on

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

回答by Theo

I ran into the same issue, but couldn't find the exact source of the problem. The jump seems to appear only in very specific conditions: If the view animates from a transform t1to a transform t2and both transforms are a combination of a scale and a translation (that's exactly your case). Given the following workaround, which doesn't make sense to me, I assume it's a bug in Core Animation.

我遇到了同样的问题,但找不到问题的确切根源。跳跃似乎只出现在非常特定的条件下:如果视图从变换t1到变换进行动画处理,t2并且这两种变换都是缩放和平移的组合(这正是您的情况)。鉴于以下对我来说没有意义的解决方法,我认为这是 Core Animation 中的一个错误。

First, I tried using CATransform3Dinstead of CGAffineTransform.

首先,我尝试使用CATransform3D代替CGAffineTransform.

Old code:

旧代码:

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

New code:

新代码:

var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform

The new code should be equivalent to the old one (the fourth parameter is set to 1.0or 0so that there is no scaling/translation in zdirection), and in fact it shows the same jumping. However, here comes the black magic: In the scale transformation, change the zparameter to anything different from 1.0, like this:

新代码应该与旧代码等效(第四个参数设置为1.0or0以便在z方向上没有缩放/平移),实际上它显示相同的跳跃。然而,黑魔法来了:在比例变换中,将z参数更改为任何不同于的参数1.0,如下所示:

transform = CATransform3DScale(transform, 1.1, 1.1, 1.01)

This parameter should have no effect, but now the jump is gone.

这个参数应该没什么作用,但是现在跳转没了。

?

?

回答by k06a

Looks like Apple UIView animation internal bug. When Apple interpolates CGAffineTransformchanges between two values to create animation it should do following steps:

看起来像 Apple UIView 动画内部错误。当 AppleCGAffineTransform在两个值之间插入变化以创建动画时,它应该执行以下步骤:

  • Extract translation, scale, and rotation
  • Interpolate extracted values form start to end
  • Assemble CGAffineTransformfor each interpolation step
  • 提取平移、缩放和旋转
  • 从开始到结束插入提取的值
  • CGAffineTransform为每个插值步骤组装

Assembling should be in following order:

组装应按以下顺序进行:

  • Translation
  • Scaling
  • Rotation
  • 翻译
  • 缩放
  • 回转

But looks like Apple make translation after scaling and rotation. This bug should be fixed by Apple.

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

回答by lbsweek

I dont know why, but this code can work

我不知道为什么,但这段代码可以工作

update:

更新:

I successfully combine scale, translate, and rotation together, from any transform state to any new transform state.

我成功地将缩放、平移和旋转结合在一起,从任何变换状态到任何新的变换状态。

I think the transform is reinterpreted at the start of the animation.

我认为转换是在动画开始时重新解释的。

the anchor of start transform is considered in new transform, and then we convert it to old transform.

在新变换中考虑开始变换的锚点,然后我们将其转换为旧变换。

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

I also try the zScale solution, it seems also work if set zScale non-1 at the first transform or at every transform

我也尝试了 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
    }

回答by Dave Batton

Instead of CGAffineTransformMakeScale() and CGAffineTransformMakeTranslation(), which create a transform based off of CGAffineTransformIdentity (basically no transform), you want to scale and translate based on the view's current transform using CGAffineTransformScale() and CGAffineTransformTranslate(), which start with the existing transform.

代替 CGAffineTransformMakeScale() 和 CGAffineTransformMakeTranslation(),它们创建基于 CGAffineTransformIdentity 的变换(基本上没有变换),您希望使用 CGAffineTransformScale() 和 CGAffineTransformTranslate() 根据视图的当前变换进行缩放和平移,它们从现有的转变。

回答by Snit

The source of the issue is the lack of perspective information to the transform.

问题的根源在于缺乏转换的透视信息。

You can add perspective information modifying the m34property of your 3d transform

您可以添加透视信息来修改m343d 变换的属性

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