Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIView animation doesn't animate on the first try

My issue is that my UIView animation only works after the first time. I have two different sets of animations. The first set works correctly, its the second set that is giving me issues. The first set slides 4 buttons onto the screen a little farther than where I want them. (These locations I call extendedState) The second set of animations slides them a little back to the position that I want. This gives the effect of bouncing back to the right spot.

In between the two animations theres a brief second that will flash the buttons back to their starting position once the first set of animations complete. By setting that starting position to the extendedStates (the location they will be in when they stop animating), the buttons appear not to move between animations (which is good. thats what I want).

Here's the order of operations:

  1. I tap a button that launches the first set of animations (which works perfectly fine)
  2. In the animationDidStop block, I launch the second set of animations (this is the one that doesn't animate on the first try)

Here's the code for the button handler:

@IBAction func selectAnimal(sender: UIButton) {

    showButtons()
    bigCircle.enabled = false
    enableButtons()

    if(tapToSelectLabel.hidden) {
        animator.repositionButtonsToExtendedState(buttons) //sets the starting position to the extendedState so it appears not to move between animations
        animator.slideButtonsIntoScreen(buttons, delegate: self) //this is the first set of animations

    }

    if(sender.titleLabel != nil) {

        if(bigCircleLabel.text != "Choose an animal") {

            if let answer:String = bigCircleLabel.text {
                solution = game.checkAnswer(answer, question: game.threeQuestions[game.questionIndex])
            }

            showResponse()
        }
    }
}

Now here's the code inside the animationDidStop block

let buttonRects:[CGRect] = [CGRectMake(834, 120, 175, 175),
                                        CGRectMake(631, 198, 175, 175),
                                        CGRectMake(470, 365, 175, 175),
                                        CGRectMake(386, 578, 175, 175)]

            UIView.animateWithDuration(0.35, delay: 0, options: [.BeginFromCurrentState, ], animations: {
                        self.buttons[3].frame = buttonRects[3]
                    }, completion: { (value:Bool) in
                        self.buttons[3].enabled = true

                        UIView.animateWithDuration(0.25, delay: 0, options: [ ], animations: {
                            self.buttons[2].frame = buttonRects[2]
                            }, completion: { (value:Bool) in
                                self.buttons[2].enabled = true

                                UIView.animateWithDuration(0.4, delay: 0, options: [ ], animations: {
                                    self.buttons[1].frame = buttonRects[1]
                                    }, completion: { (value:Bool) in
                                        self.buttons[1].enabled = true

                                        UIView.animateWithDuration(0.5, delay: 0, options: [ ], animations: {
                                            self.buttons[0].frame = buttonRects[0]
                                            }, completion: { (value:Bool) in
                                                self.buttons[0].enabled = true
                                        })
                                })
                        })
                    })

^ This code slides the buttons a little backwards to the spot I want them to be in.

Here's the code for my animator class

animator.slideButtonsIntoScreen

func slideButtonsIntoScreen(buttons:[UIButton], delegate:UIViewController) {

    let center = CGPoint(x:delegate.view.frame.width, y:delegate.view.frame.height)
    let startAngles:[CGFloat] = [0.405, 0.75, 1.1, 1.48]
    let endAngles:[CGFloat] = [1.95, 2.25, 2.622, 2.98]
    let radii:[CGFloat] = [555, 559, 558.5, 551]
    let durations:[Double] = [1.75, 1.5, 1.25 , 1]

    for index in 0...3 {
        let path = UIBezierPath(arcCenter: center, radius: radii[index], startAngle: -startAngles[index], endAngle: -endAngles[index], clockwise: false)
        let anim = CAKeyframeAnimation(keyPath: "position")
        anim.path = path.CGPath
        anim.rotationMode = kCAAlignmentNatural
        anim.repeatCount = 0
        anim.duration = durations[index] - 0.25
        anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        anim.setValue("on", forKey: "type")
        anim.delegate = delegate
        buttons[index].layer.addAnimation(anim, forKey: "animate position along path"+String(index))
        anim.removedOnCompletion = true

    }

}

animator.repositionButtonsToExtendedState

func repositionButtonsToExtendedState(buttons:[UIButton]) {


    let buttonExtendedRects:[CGRect] = [CGRectMake(755, 155, 175, 175),
                                        CGRectMake(585, 245, 175, 175),
                                        CGRectMake(450, 405, 175, 175),
                                        CGRectMake(393, 590, 175, 175)]

    for index in 0...3 {
        buttons[index].frame = buttonExtendedRects[index]
    }
}

I set breakpoints and print statements, so I know for a fact that it reaches the animation on the first try, it just doesn't show the animation. It works exactly as it should every time after the first.

like image 462
LuKenneth Avatar asked Jun 20 '16 17:06

LuKenneth


2 Answers

Instead of using UIView.animate to move the buttons backwards into the correct spot, I just used another UIBezierPath to move it backwards along the same circle.

This solution avoids the need to keep setting the frame of the buttons, which I believe was the root of all my problems - the conflicting frame-setting-statements.

like image 101
LuKenneth Avatar answered Oct 19 '22 01:10

LuKenneth


It is typically bad practice to change frames when using auto layout. From what it seems, instead of setting the frame to be wherever the animation stops, you could just set the objects to not reset back to its original position when it completes using this:

anim.fillMode = kCAFillModeForwards
anim.removedOnCompletion = false

but be sure to place those lines BEFORE you add the animation

buttons[index].layer.addAnimation(anim, forKey: "animate position along path"+String(index))

To avoid setting the frames again when you want to move your buttons back, you should be doing basically the same animation but instead of

clockwise: false

set it to true

clockwise: true

then from there you can use your end angles as the new start angles for the movement back, and set your end angles for the movement back to wherever you need the buttons to be

like image 44
MichaelQ Avatar answered Oct 19 '22 01:10

MichaelQ