[SPOILER] Challenge Question: Sliding UIButton


#1

Hey there! I was wondering what I may be doing wrong with animating the sliding button. It seems like I’m 95% there; but basically I can get the button to slide from offscreen (on the right hand side) to the left; but when the animation completes the button seems to drop down and in.

In CurrentTimeViewController.m I have:

- (void)viewDidLoad {
	[super viewDidLoad];
	
	timeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];  // Create the button
	timeButton.frame = CGRectMake(20, 90, 280, 37);  // Set the dimensions of the button
	[timeButton setTitle:@"What time is it?" forState:UIControlStateNormal];
	[timeButton addTarget:self action:@selector(showCurrentTime:) forControlEvents:UIControlEventTouchUpInside];
	[[self view] addSubview:timeButton]; // Add button to our view
	
	CABasicAnimation *slideInFromRight = [CABasicAnimation animationWithKeyPath:@"position"];
	[slideInFromRight setFromValue:[NSValue valueWithCGPoint:CGPointMake(320, 90)]];
	[slideInFromRight setToValue:[NSValue valueWithCGPoint:CGPointMake(20, 90)]];	
	[slideInFromRight setDuration:1.0];
	CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
	[slideInFromRight setTimingFunction:tf];
	[slideInFromRight setDelegate:self];
	[[timeButton layer] addAnimation:slideInFromRight forKey:@"SlideIn"];
	
}

Any suggestions? I made sure to do basic stuff (like remove the UIButton from the XIB)…aside from that, I’m a bit puzzled here…


#2

Hi,

There is a much better explanation than I can give on the next page after the Challenge in the " for the more curious" section.

but basically the animations are only temporarily modifying the properties and as soon as they’re done it will snap back to it’s true position.

if you add
[[timeButton layer] setPosition:CGPointMake(20, 90)];

pretty much anywhere in the same method then it will be set to where your animation left it.

Gareth


#3

That did it!! Thank you so much!

Here is my revised method for those interested:

- (void)viewDidLoad {
	[super viewDidLoad];
	
	timeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
	timeButton.frame = CGRectMake(20, 108, 280, 37);
	/* Learning point	=>	Animations are only temporarily modifying properties;
	 when they are done the "animatable" properties will snap to their true location
	 
	 For this reason, we are going to set our timeButton layer to it's "final" position
	 here; although we could do this anywhere in this method
	 */
	float posX = 160.0;
	float posY = 108.0;
	float fromPosX = 320.0;
	[[timeButton layer] setPosition:CGPointMake(posX, posY)];
	[timeButton setTitle:@"What time is it?" forState:UIControlStateNormal];
	[timeButton addTarget:self action:@selector(showCurrentTime:) forControlEvents:UIControlEventTouchUpInside];
	[[self view] addSubview:timeButton];
	
	CABasicAnimation *slideInFromRight = [CABasicAnimation animationWithKeyPath:@"position"];
	[slideInFromRight setFromValue:[NSValue valueWithCGPoint:CGPointMake(fromPosX, posY)]];
	[slideInFromRight setToValue:[NSValue valueWithCGPoint:CGPointMake(posX, posY)]];	
	[slideInFromRight setDuration:1.0];
	CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
	[slideInFromRight setTimingFunction:tf];
	[slideInFromRight setDelegate:self];
	[[timeButton layer] addAnimation:slideInFromRight forKey:@"SlideIn"];


}

#4

Hey TheRobBrennan, about your last post, noticed that when switching between the tabBarControllers, every time I tap the Time tab, a new instance of timeButton is created. If I come and go 100 times, 100 buttons will be created.

So, I’ll suggest a little patch to your code. Hope you don’t mind.

       // We'll need these number for animation, so moved them outside the if block.
	float posX = 160.0;
	float posY = 108.0;
	float fromPosX = 320.0;
	
	if (!timeButton) { // <---- put a conditional here to prevent the button recreation.
		timeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
		timeButton.frame = CGRectMake(20, 108, 280, 37);
		
		[[timeButton layer] setPosition:CGPointMake(posX, posY)];
		[timeButton setTitle:@"What time is it?" forState:UIControlStateNormal];
		[timeButton addTarget:self action:@selector(showCurrentTime:) forControlEvents:UIControlEventTouchUpInside];
		[[self view] addSubview:timeButton];
		
	}
	
        // your animation code as well. 
	CABasicAnimation *slideInFromRight = [CABasicAnimation animationWithKeyPath:@"position"];
	[slideInFromRight setFromValue:[NSValue valueWithCGPoint:CGPointMake(fromPosX, posY)]];
	[slideInFromRight setToValue:[NSValue valueWithCGPoint:CGPointMake(posX, posY)]];   
	[slideInFromRight setDuration:1.0];
	CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
	[slideInFromRight setTimingFunction:tf];
	[slideInFromRight setDelegate:self];
	[[timeButton layer] addAnimation:slideInFromRight forKey:@"SlideIn"];


	[self showCurrentTime:nil]; // <--- call the showCurrentTime so the label gets updated.

Thats it. All the best!

  • George.

#5

I decided to use CATransition to accomplish this part of the challenge (the way I see it the spirit of the challenge was to use the two other CAAnimation subclasses: CAAnimationGroup and CATransition). Anyhow, this is what I did after adding an IBOutlet to the button (timeButton):

CurrentTimeViewController.m

-(void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear];
	[self showCurrentTime:nil]; 
        CATransition *animateButton = [CATransition animation];
        // CHAPTER 19 CHALLENGE: Slide the button in the view
        [animateButton setDuration:0.6];
        [animateButton setType:kCATransitionMoveIn];
        [animateButton setSubtype:kCATransitionFromLeft];
        [animateButton setTimingFunction:[CAMediaTimingFunction 
                                          functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
        [[timeButton layer] addAnimation:animateButton forKey:@"buttonAppears"];
}

The tricky part here is that we need to use setType: and setSubType: to achieve the desired effect. From Apple’s documentation: