Bronze, Silver and Gold challenge solutions


In TimeViewController.m:

[code]-(void)bounceTimeLabel {
// Create a key fram animation
CAKeyframeAnimation *bounce = [CAKeyframeAnimation animationWithKeyPath:@“transform”];

// Create the values it will pass through
CATransform3D forward = CATransform3DMakeScale(1.3, 1.3, 1);
CATransform3D back = CATransform3DMakeScale(0.7, 0.7, 1);
CATransform3D forward2 = CATransform3DMakeScale(1.2, 1.2, 1);
CATransform3D back2 = CATransform3DMakeScale(0.9, 0.9, 1);
[bounce setValues:[NSArray arrayWithObjects:
				   [NSValue valueWithCATransform3D:CATransform3DIdentity],
				   [NSValue valueWithCATransform3D:forward],
				   [NSValue valueWithCATransform3D:back],
				   [NSValue valueWithCATransform3D],
				   [NSValue valueWithCATransform3D:back2],
				   [NSValue valueWithCATransform3D:CATransform3DIdentity],

// Set the duration
[bounce setDuration:0.6];

CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"];
[opacity setFromValue:[NSNumber numberWithFloat:1]];
[opacity setToValue:[NSNumber numberWithFloat:0]];
[opacity setDuration:0.6];

CAAnimationGroup *group = [CAAnimationGroup animation];
[group setAnimations:[NSArray arrayWithObjects:bounce, opacity, nil]];
[[timeLabel layer] addAnimation:group forKey:@"bounce,opacity"];

// Animate the layer
//[[timeLabel layer] addAnimation:bounce
//						 forKey:@"bounceAnimation"];


Open up TimeViewController.xib and Control-Click TimeViewController.h to open it up in the Assistant Editor. Control-Drag from the button to between the curly brackets to create an outlet, like this:

and add a new method (still in TimeViewController.h):

In TimeViewController.m:

[code]-(void)slideTimeButton {
CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@“position”];
[position setDelegate:self];
[position setFromValue:[NSValue valueWithCGPoint:CGPointMake(0, [[timeButton layer] position].y)]];
[position setToValue:[NSValue valueWithCGPoint:CGPointMake([[timeButton layer] position].x, [[timeButton layer] position].y)]];
[position setDuration:0.5];

CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[position setTimingFunction:tf];

[[timeButton layer] addAnimation:position forKey:@"positionAnimation"];


In TimeViewController.h:

In TimeViewController.m:

[code]-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
[self pulseTimeButton];

-(void)pulseTimeButton {
CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@“opacity”];

[opacity setFromValue:[NSNumber numberWithFloat:1]];
[opacity setToValue:[NSNumber numberWithFloat:0]];
[opacity setDuration:0.6];
[opacity setRepeatCount:HUGE_VALF];
[opacity setAutoreverses:YES];

[[timeButton layer] addAnimation:opacity forKey:@"opacityAnimation"];


Notice that I didn’t add any form of checks inside -animationDidStop:finished:, but that’s just because for this, I didn’t have to.


For the silver challenge, where did you put the method call slideTimeButton?

I put it in my viewWillAppear but it doesn’t slide in every time the TimeViewController appears.


[quote=“billyshih”]For the silver challenge, where did you put the method call slideTimeButton?

I put it in my viewWillAppear but it doesn’t slide in every time the TimeViewController appears.[/quote]

I put it inside showCurrentTime:, like so:

[code]-(IBAction)showCurrentTime:(id)sender {
NSDate *now = [NSDate date];

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
[timeLabel setText:[formatter stringFromDate:now]];

//[self spinTimeLabel];
[self bounceTimeLabel];
[self slideTimeButton];




I did the bronze challenge slightly differently. I used a key frame animation rather than a basic animation to try to match the opacity more closely to the size of the label; in the bounceTimeLabel method of TimeViewController.m underneath the bounce setup:

[code]CAKeyframeAnimation *fader = [CAKeyframeAnimation animationWithKeyPath:@“opacity”];
[fader setValues:[NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:0.8], nil]];
[fader setDuration:0.6];

// Two animations concurrently so set up CAAnimationGroup
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration:0.6];
[group setAnimations:[NSArray arrayWithObjects:bounce, fader, nil]];

// Animate the layer
[[timeLabel layer] addAnimation:group forKey:@"bounceAndFade"];

I also tackled the silver challenge in a different way. I set up an outlet for the button, added a new method and called it in the showCurrentTime method like TheEskil did, but then instead of using a basic animation, I used a transition. I also added a simple BOOL to track if the button had made the transition. I wanted the button to slide in each time the view appeared, but once it had slided in, if you pressed the button again, it wouldn’t slide in again. So my slideTimeButton method looks like:

[code]- (void)slideTimeButton
// Silver Challenge - Button slide
if (slidedIn)
CATransition *transition = [CATransition animation];
[transition setType:kCATransitionMoveIn];
[transition setSubtype:kCATransitionFromLeft];
[transition setDuration:0.5];
CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[transition setTimingFunction:tf];

[[timeButton layer] addAnimation:transition forKey:@"slideIn"];

slidedIn = YES;

and my viewWillAppear method looks like:

[code]- (void)viewWillAppear:(BOOL)animated
NSLog(@“Current TimeViewController will appear”);
[super viewWillAppear];

// Silver Challenge - Button slide
slidedIn = NO;
[self showCurrentTime:nil];


A quick query; why does the bounce animation only work the first time you go to the time view unless you add: [bounce setDelegate:self]; to the bounce object? Once this line is added, it bounces each time you go to the time view.

On with the gold challenge now!




My gold challenge solution - similar to TheEskil but I did add logic in the animationDidStop method as this method is called when the label spin finishes as well as when the button slide finishes. Therefore to make sure the button pulse is called after the correct animation has finished my animationDidStop method is:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if ([[anim valueForKey:@"id"] isEqual:@"slide"]) { [self pulseTimeButton]; } } this is called after the slideTimeButton has slider in:

[code]- (void)slideTimeButton
// Silver Challenge - Button slide
if (slidedIn)
CATransition *transition = [CATransition animation];
[transition setDelegate:self];
[transition setValue:@“slide” forKey:@“id”];
[transition setType:kCATransitionMoveIn];
[transition setSubtype:kCATransitionFromLeft];
[transition setDuration:2.5];
CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[transition setTimingFunction:tf];

[[timeButton layer] addAnimation:transition forKey:@"slideIn"];

slidedIn = YES;

And I used [transition setValue:@“slide” forKey:@“id”]; to differentiate between animations.

My pulse code:

[code]- (void)pulseTimeButton
CABasicAnimation *pulse = [CABasicAnimation animationWithKeyPath:@“opacity”];
[pulse setFromValue:[NSNumber numberWithFloat:1.0]];
[pulse setToValue:[NSNumber numberWithFloat:0.2]];

CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[pulse setTimingFunction:tf];

[pulse setDuration:1.0];
[pulse setAutoreverses:YES];
[pulse setRepeatCount:HUGE_VALF];
[[timeButton layer] addAnimation:pulse forKey:@"pulse"];

I just added an ease in and ease out and didn’t totally fade button out as thought it looked better.



Does anyone have a working solution for the Silver Challenge where the What time is it? button slides into position from the left each time the TimeView appears? It’s not clear to me which if any of these previously posted solution does … and I haven’t been able to make that work yet either! :confused:


@dmddmd Override viewDidAppear with the slideTimeButton method. The button should slide in every time you select the Time tab:

- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear]; [self slideTimeButton]; // Silver challenge }


For those who are having trouble animating the button more than once (i.e. button animates only the first time the tab is viewed). I discovered that adding the following line in viewDidDisappear :

    [[timeButton layer] removeAllAnimations];

made sure that the animation was playing again every time that viewWillAppear was called.


A simple way to do the silver challenge is to use view animations. Assuming the button is intially off-screen (eg. in interface builder set its origin’s x-coordinate to 400), then in viewDidAppear:, do:

[UIView beginAnimations:nil context:NULL]; = (CGPoint){160,}; [UIView commitAnimations];

In viewDidDisappear: you can send the button back off-screen by setting its center property appropriately, so that the same animation will play next time the current time view appears.


I hadn’t read the gold challenge when I did the above, and it seems that I would’ve had to use some deprecated methods if I wanted to complete the gold challenge by building on the previous code. Anyway, that led me to block-based view animations, and the following code in the viewDidAppear: method achieves the goal nicely:

[UIView animateWithDuration:1.0 animations:^{
CGPoint center =; // the button is off-screen atm
center.x = 160; = center;
} completion:^(BOOL finished){
if (finished)
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
button.alpha = 0.0;
} completion:nil];


(In the viewDidDisappear: method, send the button back offscreen, and also set its alpha to 1 so that the whole thing repeats itself whenever the time view appears)


Well everyone seems to have posted a lot of different solutions here, I just wanted to thank bananaSkin, setting the transitionAnimation’s delegate to self made the button animate everytime, however this did not work for the bouncy animation of the timeLabel. Also wanted to add that placing the animation call in ViewWillAppear is probably an easier solution to your problem that the animation got called every time the button was pressed.



If you set the group animation to delegate as well, it should every time as well.


Hi all,

One piece of info that I haven’t seen anyone mention yet, from Apple’s docs:

So, instead of using delegation and -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag, I am just telling the pulse animation to begin 0.5 seconds after the slide animation finishes, like so:

    // After the code for the slide animation and pulse animation
    // Obtain the internal time for this layer
    CFTimeInterval now = [[timeButton layer] convertTime:CACurrentMediaTime() fromLayer:nil];
    [pulse setBeginTime:now + [slide duration] + 0.5];

    // Proceed to add both animations to the button layer (not using groups)...

Notice that I’m using [slide duration], so that the code is future proof to changes in the slide duration.

Still, I believe the best way to to this challenge is to use CATransaction, and use a completion block to get the chain animation effect.




Yes, the key is to call the slide function from viewDidAppear. Also, my solution is only slightly different from others above in that I only manipulate the x coordinate in my animation by using the keyPath “transform.translation.x”. There is no need to mess with the y coordinate since it remains the same throughout.

[code]- (void)viewDidAppear:(BOOL)animated
[self slideTimeButtonIntoView];

  • (void)slideTimeButtonIntoView
    // move left by amount of its original x position and restore to original location
    CABasicAnimation *slideFromLeft = [CABasicAnimation animationWithKeyPath:@“transform.translation.x”];
    [slideFromLeft setDuration:0.6]; // seconds
    [slideFromLeft setFromValue:[NSNumber numberWithFloat:-[[timeButton layer] position].x]];
    [slideFromLeft setToValue:[NSNumber numberWithFloat:0.0]];
    CAMediaTimingFunction *easeToStop = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    [slideFromLeft setTimingFunction:easeToStop];
    [[timeButton layer] addAnimation:slideFromLeft forKey:@“slideFromLeft”];