Reputation: 11598
I'm trying to write a custom UIButton
subclass that will "animate" during press and release.
When pressed, the button should "shrink" (toward its center) to 90% of its original size. When released, the button should "expand" to 105%, shrink again to 95% and then return to its original size.
Here's the code I've got right now:
#pragma mark -
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self animatePressedDown];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self animateReleased];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self animateReleased];
}
- (void)animatePressedDown {
NSLog(@"button.frame before animatedPressedDown: %@", NSStringFromCGRect(self.frame));
[self addShadowLayer];
CATransform3D ninetyPercent = CATransform3DMakeScale(0.90f, 0.90f, 1.00f);
[UIView animateWithDuration:0.2f
animations:^{
self.layer.transform = ninetyPercent;
}
completion:^(BOOL finished) {
NSLog(@"button.frame after animatedPressedDown: %@", NSStringFromCGRect(self.frame));
}
];
}
- (void)animateReleased {
[self.shadowLayer removeFromSuperlayer];
CATransform3D oneHundredFivePercent = CATransform3DMakeScale(1.05f, 1.05f, 1.00f);
CATransform3D ninetyFivePercent = CATransform3DMakeScale(0.95f, 0.95f, 1.00f);
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = oneHundredFivePercent;
}
completion:^(BOOL finished) {
NSLog(@"button.frame after animateReleased (Stage 1): %@", NSStringFromCGRect(self.frame));
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = ninetyFivePercent;
}
completion:^(BOOL finished) {
NSLog(@"button.frame after animateReleased (Stage 2): %@", NSStringFromCGRect(self.frame));
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = CATransform3DIdentity;
self.layer.frame = self.frame;
}
completion:^(BOOL finished) {
NSLog(@"button.frame after animateReleased (Stage 3): %@", NSStringFromCGRect(self.frame));
}
];
}
];
}
];
}
Anyway, the above code works perfectly... some of the time. At other times, the button animation works as expected, but after the "released" animation, the final frame of the button is "shifted" up and left from its original position. That's why I have those NSLog
statements, to track exactly where the button's frame is during each stage of the animation. When the "shift" occurs, it happens somewhere between animatePressedDown
and animateReleased
. At least, the frame shown in animatePressedDown
is ALWAYS what I expect it to be, but the first value for the frame in animateReleased
is wrong frequently.
I can't see a pattern to the madness, though the same buttons in my app tend to behave correctly or incorrectly consistently from app run to app run.
I'm using auto layout for all of my buttons, so I can't figure out what the difference is to make one button behave correctly and another one change its position.
Upvotes: 3
Views: 1322
Reputation: 11598
I really hate when I answer my own questions, but I figured it out.
(I'd been struggling with this issue for days, but it finally clicked after I asked the question. Isn't that how it always works?)
Apparently, the problem for me was the fact that (on the buttons that were "shifting") I was changing the button's title mid-animation.
Once I set up a blocks-based system to call the title change only after the button's "bounce"/"pop" animation was complete, the problems went away.
I still don't know all of why this works the way it does, as the titles I was setting in no way changed the overall size of the buttons, but setting the buttons up as described fixed my problem.
Here's the code from my custom UIButton
subclass, with the block property added:
@interface MSSButton : UIButton {
}
@property (copy , nonatomic) void(^pressedCompletion)(void);
// Other, non-related stuff...
@end
@implementation MSSButton
#pragma mark -
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self animatePressedDown];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self animateReleased];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self animateReleased];
}
- (void)animatePressedDown {
[self addShadowLayer];
CATransform3D ninetyPercent = CATransform3DMakeScale(0.90f, 0.90f, 1.00f);
[UIView animateWithDuration:0.2f
animations:^{
self.layer.transform = ninetyPercent;
}
];
}
- (void)animateReleased {
[self.shadowLayer removeFromSuperlayer];
CATransform3D oneHundredFivePercent = CATransform3DMakeScale(1.05f, 1.05f, 1.00f);
CATransform3D ninetyFivePercent = CATransform3DMakeScale(0.95f, 0.95f, 1.00f);
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = oneHundredFivePercent;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = ninetyFivePercent;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = CATransform3DIdentity;
}
completion:^(BOOL finished) {
if (self.pressedCompletion != nil) {
self.pressedCompletion();
self.pressedCompletion = nil;
}
}
];
}
];
}
];
}
@end
Since my IBAction
fires before animateReleased
(due to order of methods listed in my touch handling methods), I simply set the pressedCompletion
block in my IBAction
and the title is changed at the end of the animation sequence.
Upvotes: 3
Reputation: 124997
One likely problem is that you're trying to use the view's frame
property after you've changed the transform
property. The UIView
class reference page specifically warns against that:
Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
Specifically, you're changing the layer's transform
, which probably impacts the view's transform
, and then accessing self.frame
. You might want to ensure that the view's transform
is set to CGAffineTransformIdentity
before accessing self.frame
.
Upvotes: 0