iOSカスタムUIButtonクリックアニメーション特効——HEROブログ


関連資料を参考にして、面白いbuttonアニメーション効果を整理しました.
まず効果図を見てみましょう.
コードを貼り付けます.
ViewController:
#import 

@interface ViewController : UIViewController

@end

#import "ViewController.h"
#import "HWButton.h"

#define mainW [UIScreen mainScreen].bounds.size.width
#define mainH [UIScreen mainScreen].bounds.size.height

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor blackColor];

    // 
    [self creatButton];
}

- (void)creatButton
{
    HWButton *button = [[HWButton alloc] initWithFrame:CGRectMake(mainW * 0.5 - 60, mainH - 100, 120, 72) maxLeft:100 maxRight:100 maxHeight:300];
    [button setImage:[UIImage imageNamed:@"button"] forState:UIControlStateNormal];
    button.images = @[[UIImage imageNamed:@"Circle 1"], [UIImage imageNamed:@"Circle 2"], [UIImage imageNamed:@"Circle 3"], [UIImage imageNamed:@"Hero"]];
    button.duration = 10;
    [button addTarget:self action:@selector(buttonOnClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonOnClick:(HWButton *)btn
{
    [btn generateBubbleInRandom];
}

@end

HWButton:
#import 

@interface HWButton : UIButton

@property (nonatomic, assign) CGFloat maxLeft;
@property (nonatomic, assign) CGFloat maxRight;
@property (nonatomic, assign) CGFloat maxHeight;
@property (nonatomic, assign) CGFloat duration;
@property (nonatomic, strong) NSArray *images;

- (instancetype)initWithFrame:(CGRect)frame maxLeft:(CGFloat)maxLeft maxRight:(CGFloat)maxRight maxHeight:(CGFloat)maxHeight;

- (void)generateBubbleWithImage:(UIImage *)image;

- (void)generateBubbleInRandom;

@end

#import "HWButton.h"

@implementation HWButton
{
    CGPoint _startPoint;
    CGFloat _maxWidth;
    NSMutableSet *_recyclePool;
    NSMutableArray *_array;
}

- (instancetype)initWithFrame:(CGRect)frame maxLeft:(CGFloat)maxLeft maxRight:(CGFloat)maxRight maxHeight:(CGFloat)maxHeight
{
    self = [super initWithFrame:frame];
    if (self) {
        _maxHeight = maxHeight;
        _maxLeft   = maxLeft;
        _maxRight  = maxRight;
        
        [self initData];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self initData];
    }
    return self;
}

- (void)initData
{
    _array = @[].mutableCopy;
    _recyclePool = [NSMutableSet set];
}

- (void)generateBubbleInRandom
{
    CALayer *layer;
    
    if (_recyclePool.count > 0) {
        layer = [_recyclePool anyObject];
        
        [_recyclePool removeObject:layer];
        
    }else {
        UIImage *image = self.images[arc4random() % self.images.count];
        
        layer = [self createLayerWithImage:image];
    }
    
    [self.layer addSublayer:layer];
    [self generateBubbleWithCAlayer:layer];
}

- (void)generateBubbleWithImage:(UIImage *)image
{
    CALayer *layer = [self createLayerWithImage:image];
    
    [self.layer addSublayer:layer];
    [self generateBubbleWithCAlayer:layer];
}

- (void)generateBubbleWithCAlayer:(CALayer *)layer
{
    _maxWidth = _maxLeft + _maxRight + self.bounds.size.width;
    
    _startPoint = CGPointMake(self.frame.size.width / 2, 0);
    
    CGPoint endPoint = CGPointMake(_maxWidth * [self randomFloat] - _maxLeft, -_maxHeight);
    CGPoint controlPoint1 = CGPointMake(_maxWidth * [self randomFloat] - _maxLeft, -_maxHeight * 0.2);
    CGPoint controlPoint2 = CGPointMake(_maxWidth * [self randomFloat] - _maxLeft, -_maxHeight * 0.6);
    
    CGMutablePathRef curvedPath = CGPathCreateMutable();
    CGPathMoveToPoint(curvedPath, NULL, _startPoint.x, _startPoint.y);
    CGPathAddCurveToPoint(curvedPath, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
    
    CAKeyframeAnimation *keyFrame = [CAKeyframeAnimation animation];
    keyFrame.keyPath = @"position";
    keyFrame.path = CFAutorelease(curvedPath);
    keyFrame.duration = self.duration;
    keyFrame.calculationMode = kCAAnimationPaced;
    
    [layer addAnimation:keyFrame forKey:@"keyframe"];
    
    CABasicAnimation *scale = [CABasicAnimation animation];
    scale.keyPath = @"transform.scale";
    scale.toValue = @1;
    scale.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 0.1)];
    scale.duration = 0.5;
    
    CABasicAnimation *alpha = [CABasicAnimation animation];
    alpha.keyPath = @"opacity";
    alpha.fromValue = @1;
    alpha.toValue = @0.1;
    alpha.duration = self.duration * 0.4;
    alpha.beginTime = self.duration - alpha.duration;
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = @[keyFrame, scale, alpha];
    group.duration = self.duration;
    group.delegate = self;
    group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    [layer addAnimation:group forKey:@"group"];
    
    [_array addObject:layer];
}

- (CGFloat)randomFloat
{
    return (arc4random() % 100)/100.0f;
}

- (CALayer *)createLayerWithImage:(UIImage *)image
{
    CGFloat scale = [UIScreen mainScreen].scale;
    CALayer *layer = [CALayer layer];
    layer.frame    = CGRectMake(0, 0, image.size.width / scale, image.size.height / scale);
    layer.contents = (__bridge id)image.CGImage;;
    return layer;
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if (flag) {
        CALayer *layer = [_array firstObject];
        [layer removeAllAnimations];
        [layer removeFromSuperlayer];
        [_array removeObject:layer];
        [_recyclePool addObject:layer];
    }
}

@end