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:
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With