CGAffineTransform scale and translation - jump before animation

后端 未结 5 1072
孤独总比滥情好
孤独总比滥情好 2021-02-01 03:43

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 vie

相关标签:
5条回答
  • 2021-02-01 04:15

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

    • Extract translation, scale, and rotation
    • Interpolate extracted values form start to end
    • Assemble CGAffineTransform for each interpolation step

    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.

    0 讨论(0)
  • 2021-02-01 04:23

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

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

    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
    
    0 讨论(0)
  • 2021-02-01 04:29

    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 t1 to a transform t2 and 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.

    First, I tried using CATransform3D instead of 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.0 or 0 so that there is no scaling/translation in z direction), and in fact it shows the same jumping. However, here comes the black magic: In the scale transformation, change the z parameter to anything different from 1.0, like this:

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

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

    0 讨论(0)
  • 2021-02-01 04:30

    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.

    0 讨论(0)
  • 2021-02-01 04:34

    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

        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 讨论(0)
提交回复
热议问题