UIButton animation in the press
I am trying to write my own subclass UIButton
that will "animate" when pressed and released.
When pressed, the button should "shrink" (towards its center) to 90% of its original size. On release, the button should "expand" to 105%, shrink again to 95%, and then return to its original size.
Here is the code I 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 great ... for a while. In other cases, the button animation works as expected, but after the animation is "released", the final frame of the button is "pushed" up and out of its original position. This is why I have those operators NSLog
to keep track of exactly where the button frame is at each stage of the animation. When a "shift" occurs, it is somewhere between animatePressedDown
and animateReleased
. At least the frame shown in animatePressedDown
is ALWAYS what I expect, but the first value for the frame in animateReleased
is often wrong.
I do not see the madness pattern, although the same buttons in my application behave correctly or incorrectly sequentially from application launch to application.
I use an automaton for all of my buttons, so I can't figure out what the difference is, so that one button behaves correctly and the other changes its position.
source to share
I really hate it when I answer my own questions, but I figured it out.
(I wrestled with this issue for a few days, but finally clicked after I asked a question. Isn't that how it works?)
Apparently the problem for me was that (on buttons that were "offset") I was changing the average animation of the button title.
Once I set up the block-based system to call the name change only after the bounce / pop button animation was complete, the problems went away.
I still don't know why it works the way it does, since the headers I was customizing didn't change the overall size of the buttons in any way, but setting the buttons up as described fixed my problem.
Here's the code from my custom subclass UIButton
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 mine IBAction
fires before animateReleased
(due to the order of the methods listed in my touch handling methods), I just set the block pressedCompletion
to mine IBAction
and the title changes at the end of the animation.
source to share
The likely problem is that you are trying to use the view property frame
after changing the property transform
. The link to UIView
specifically warns of this :
Warning. If the conversion property is not an identity conversion, the value of that property is undefined and should therefore be ignored.
Specifically, you change the layer transform
that probably affects the view transform
and then access to self.frame
. You can be sure that before you can view self.frame
before submitting the transform
set CGAffineTransformIdentity
.
source to share