Search code examples
iosswiftcore-animation

Translate transformation applied while only scale transformation added


I am creating an animation that I want to use when the app is retrieving some data online. The idea is that I have some dots in a row, they will be scaled smaller that their original sizes then return to their original size and all of this with a small delay between each scaling. The animation is repeated and use auto-reverse mode.

To do that I create some dots using a core graphic method, add them to a view and position them using a CGAffineTransformMakeTranslation transformation. Then I use a loop to animate them one by one with a delay and I use a CGAffineTransformScale transformation for scaling.

Problem: I don't get the expected animation (at least what I'm expecting). When the dots are being scaled, they also move back to their original position.

Can someone enlighten me why there is a translate transformation while in the UIView animation, I'm only specifying a scaling?

enter image description here

Here is the code:

private var dots = [UIImage]()

public init(numberOfDots: Int, hexaColor: Int, dotDiameter: CGFloat = 30, animationDuration: NSTimeInterval = 1) {
    self.dotDiameter = dotDiameter
    self.animationDuration = animationDuration

    for _ in 0 ..< numberOfDots {
        dots.append(GraphicHelper.drawDisk(hexaColor, rectForDisk: CGRect(x: 0, y: 0, width: dotDiameter, height: dotDiameter), withStroke: false))
    }

    let spaceBetweenDisks: CGFloat = dotDiameter / 3
    let viewWidth: CGFloat = CGFloat(numberOfDots) * dotDiameter + CGFloat(numberOfDots - 1) * spaceBetweenDisks
    super.init(frame: CGRectMake(0, 0, viewWidth, dotDiameter))

    setup()
}

private func setup() {
    for (i, dot) in dots.enumerate() {
        let dotImageView = UIImageView(image: dot)

        addSubview(dotImageView)

        let spaceBetweenDisks: CGFloat = dotDiameter / 3
        let xOffset = CGFloat(i) * (dotDiameter + spaceBetweenDisks)

        dotImageView.transform = CGAffineTransformMakeTranslation(xOffset, 0)
    }
}

public func startAnimation() {
    for i in 0 ..< self.dots.count {
        let dotImageView: UIImageView = self.subviews[i] as! UIImageView
        let transformBeforeAnimation = dotImageView.transform

        let delay: NSTimeInterval = NSTimeInterval(i)/NSTimeInterval(self.dots.count) * animationDuration

        UIView.animateWithDuration(animationDuration, delay: delay, options: [UIViewAnimationOptions.Repeat, UIViewAnimationOptions.Autoreverse], animations: {

            dotImageView.transform = CGAffineTransformScale(dotImageView.transform, 0.05, 0.05)

            }, completion: { finished in
                dotImageView.transform = CGAffineTransformIdentity
                dotImageView.transform = transformBeforeAnimation
        })
    }
}

EDIT:

I found a fix but I don't understand how come it's fixing it. So if anyone can explain.

I added these 2 lines:

dotImageView.transform = CGAffineTransformIdentity
dotImageView.transform = transformBeforeAnimation

before this line in startAnimation:

dotImageView.transform = CGAffineTransformScale(dotImageView.transform, 0.05, 0.05)

Solution

  • Combining translate and scale transforms is confusing and hard to get right.

    I have to spend far too much time with graph paper and deep thought in order to figure it out, and I'm too tired for that right now.

    Don't do that. Place your dot image views by moving their center coordinates, and leave the transform at identity. Then when you scale them they should scale in place like you want.

    Note that if you want them to move and scale at the same time you can both alter the view's center property and it's transform scale in the same animateWithDuration call and it works correctly. (Not so with changing the frame by the way. If you change the transform then the frame property doesn't work correctly any more. Apple's docs say that the results of reading/writing the frame property of a view with a non-identity transform are "undefined".)