单军华
2018-07-19 83b9d5c682b21d88133f24da0f94dd56bd79e687
screendisplay/Pods/DWBubbleMenuButton/Source/DWBubbleMenuButton.m
New file
@@ -0,0 +1,518 @@
//
//  DWBubbleMenuButton.m
//  DWBubbleMenuButtonExample
//
//  Created by Derrick Walker on 10/8/14.
//  Copyright (c) 2014 Derrick Walker. All rights reserved.
//
#import "DWBubbleMenuButton.h"
#define kDefaultAnimationDuration 0.25f
@interface DWBubbleMenuButton ()
@property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer;
@property (nonatomic, strong) NSMutableArray *buttonContainer;
@property (nonatomic, assign) CGRect originFrame;
@end
@implementation DWBubbleMenuButton
#pragma mark -
#pragma mark Public Methods
- (void)addButtons:(NSArray *)buttons {
    assert(buttons != nil);
    for (UIButton *button in buttons) {
        [self addButton:button];
    }
    if (self.homeButtonView != nil) {
        [self bringSubviewToFront:self.homeButtonView];
    }
}
- (void)addButton:(UIButton *)button {
    assert(button != nil);
    if (_buttonContainer == nil) {
        self.buttonContainer = [[NSMutableArray alloc] init];
    }
    if ([_buttonContainer containsObject:button] == false) {
        [_buttonContainer addObject:button];
        [self addSubview:button];
        button.hidden = YES;
    }
}
- (void)showButtons {
    if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonWillExpand:)]) {
        [self.delegate bubbleMenuButtonWillExpand:self];
    }
    [self _prepareForButtonExpansion];
    self.userInteractionEnabled = NO;
    [CATransaction begin];
    [CATransaction setAnimationDuration:_animationDuration];
    [CATransaction setCompletionBlock:^{
        for (UIButton *button in _buttonContainer) {
            button.transform = CGAffineTransformIdentity;
        }
        if (self.delegate != nil) {
            if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonDidExpand:)]) {
                [self.delegate bubbleMenuButtonDidExpand:self];
            }
        }
        self.userInteractionEnabled = YES;
    }];
    NSArray *buttonContainer = _buttonContainer;
    if (self.direction == DirectionUp || self.direction == DirectionLeft) {
        buttonContainer = [self _reverseOrderFromArray:_buttonContainer];
    }
    for (int i = 0; i < buttonContainer.count; i++) {
        int index = (int)buttonContainer.count - (i + 1);
        UIButton *button = [buttonContainer objectAtIndex:index];
        button.hidden = NO;
        // position animation
        CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
        CGPoint originPosition = CGPointZero;
        CGPoint finalPosition = CGPointZero;
        switch (self.direction) {
            case DirectionLeft:
                originPosition = CGPointMake(self.frame.size.width - self.homeButtonView.frame.size.width, self.frame.size.height/2.f);
                finalPosition = CGPointMake(self.frame.size.width - self.homeButtonView.frame.size.width - button.frame.size.width/2.f - self.buttonSpacing
                                            - ((button.frame.size.width + self.buttonSpacing) * index),
                                            self.frame.size.height/2.f);
                break;
            case DirectionRight:
                originPosition = CGPointMake(self.homeButtonView.frame.size.width, self.frame.size.height/2.f);
                finalPosition = CGPointMake(self.homeButtonView.frame.size.width + self.buttonSpacing + button.frame.size.width/2.f
                                            + ((button.frame.size.width + self.buttonSpacing) * index),
                                            self.frame.size.height/2.f);
                break;
            case DirectionUp:
                originPosition = CGPointMake(self.frame.size.width/2.f, self.frame.size.height - self.homeButtonView.frame.size.height);
                finalPosition = CGPointMake(self.frame.size.width/2.f,
                                            self.frame.size.height - self.homeButtonView.frame.size.height - self.buttonSpacing - button.frame.size.height/2.f
                                            - ((button.frame.size.height + self.buttonSpacing) * index));
                break;
            case DirectionDown:
                originPosition = CGPointMake(self.frame.size.width/2.f, self.homeButtonView.frame.size.height);
                finalPosition = CGPointMake(self.frame.size.width/2.f,
                                            self.homeButtonView.frame.size.height + self.buttonSpacing + button.frame.size.height/2.f
                                            + ((button.frame.size.height + self.buttonSpacing) * index));
                break;
            default:
                break;
        }
        positionAnimation.duration = _animationDuration;
        positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        positionAnimation.fromValue = [NSValue valueWithCGPoint:originPosition];
        positionAnimation.toValue = [NSValue valueWithCGPoint:finalPosition];
        positionAnimation.beginTime = CACurrentMediaTime() + (_animationDuration/(float)_buttonContainer.count * (float)i);
        positionAnimation.fillMode = kCAFillModeForwards;
        positionAnimation.removedOnCompletion = NO;
        [button.layer addAnimation:positionAnimation forKey:@"positionAnimation"];
        button.layer.position = finalPosition;
        // scale animation
        CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        scaleAnimation.duration = _animationDuration;
        scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        scaleAnimation.fromValue = [NSNumber numberWithFloat:0.01f];
        scaleAnimation.toValue = [NSNumber numberWithFloat:1.f];
        scaleAnimation.beginTime = CACurrentMediaTime() + (_animationDuration/(float)_buttonContainer.count * (float)i) + 0.03f;
        scaleAnimation.fillMode = kCAFillModeForwards;
        scaleAnimation.removedOnCompletion = NO;
        [button.layer addAnimation:scaleAnimation forKey:@"scaleAnimation"];
        button.transform = CGAffineTransformMakeScale(0.01f, 0.01f);
    }
    [CATransaction commit];
    _isCollapsed = NO;
}
- (void)dismissButtons {
    if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonWillCollapse:)]) {
        [self.delegate bubbleMenuButtonWillCollapse:self];
    }
    self.userInteractionEnabled = NO;
    [CATransaction begin];
    [CATransaction setAnimationDuration:_animationDuration];
    [CATransaction setCompletionBlock:^{
        [self _finishCollapse];
        for (UIButton *button in _buttonContainer) {
            button.transform = CGAffineTransformIdentity;
            button.hidden = YES;
        }
        if (self.delegate != nil) {
            if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonDidCollapse:)]) {
                [self.delegate bubbleMenuButtonDidCollapse:self];
            }
        }
        self.userInteractionEnabled = YES;
    }];
    int index = 0;
    for (int i = (int)_buttonContainer.count - 1; i >= 0; i--) {
        UIButton *button = [_buttonContainer objectAtIndex:i];
        if (self.direction == DirectionDown || self.direction == DirectionRight) {
            button = [_buttonContainer objectAtIndex:index];
        }
        // scale animation
        CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        scaleAnimation.duration = _animationDuration;
        scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        scaleAnimation.fromValue = [NSNumber numberWithFloat:1.f];
        scaleAnimation.toValue = [NSNumber numberWithFloat:0.01f];
        scaleAnimation.beginTime = CACurrentMediaTime() + (_animationDuration/(float)_buttonContainer.count * (float)index) + 0.03;
        scaleAnimation.fillMode = kCAFillModeForwards;
        scaleAnimation.removedOnCompletion = NO;
        [button.layer addAnimation:scaleAnimation forKey:@"scaleAnimation"];
        button.transform = CGAffineTransformMakeScale(1.f, 1.f);
        // position animation
        CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
        CGPoint originPosition = button.layer.position;
        CGPoint finalPosition = CGPointZero;
        switch (self.direction) {
            case DirectionLeft:
                finalPosition = CGPointMake(self.frame.size.width - self.homeButtonView.frame.size.width, self.frame.size.height/2.f);
                break;
            case DirectionRight:
                finalPosition = CGPointMake(self.homeButtonView.frame.size.width, self.frame.size.height/2.f);
                break;
            case DirectionUp:
                finalPosition = CGPointMake(self.frame.size.width/2.f, self.frame.size.height - self.homeButtonView.frame.size.height);
                break;
            case DirectionDown:
                finalPosition = CGPointMake(self.frame.size.width/2.f, self.homeButtonView.frame.size.height);
                break;
            default:
                break;
        }
        positionAnimation.duration = _animationDuration;
        positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        positionAnimation.fromValue = [NSValue valueWithCGPoint:originPosition];
        positionAnimation.toValue = [NSValue valueWithCGPoint:finalPosition];
        positionAnimation.beginTime = CACurrentMediaTime() + (_animationDuration/(float)_buttonContainer.count * (float)index);
        positionAnimation.fillMode = kCAFillModeForwards;
        positionAnimation.removedOnCompletion = NO;
        [button.layer addAnimation:positionAnimation forKey:@"positionAnimation"];
        button.layer.position = originPosition;
        index++;
    }
    [CATransaction commit];
    _isCollapsed = YES;
}
#pragma mark -
#pragma mark Private Methods
- (void)_defaultInit {
    self.clipsToBounds = YES;
    self.layer.masksToBounds = YES;
    self.direction = DirectionUp;
    self.animatedHighlighting = YES;
    self.collapseAfterSelection = YES;
    self.animationDuration = kDefaultAnimationDuration;
    self.standbyAlpha = 1.f;
    self.highlightAlpha = 0.45f;
    self.originFrame = self.frame;
    self.buttonSpacing = 20.f;
    _isCollapsed = YES;
    self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTapGesture:)];
    self.tapGestureRecognizer.cancelsTouchesInView = NO;
    self.tapGestureRecognizer.delegate = self;
    [self addGestureRecognizer:self.tapGestureRecognizer];
}
- (void)_handleTapGesture:(id)sender {
    if (self.tapGestureRecognizer.state == UIGestureRecognizerStateEnded) {
        CGPoint touchLocation = [self.tapGestureRecognizer locationOfTouch:0 inView:self];
        if (_collapseAfterSelection && _isCollapsed == NO && CGRectContainsPoint(self.homeButtonView.frame, touchLocation) == false) {
            [self dismissButtons];
        }
    }
}
- (void)_animateWithBlock:(void (^)(void))animationBlock {
    [UIView transitionWithView:self
                      duration:kDefaultAnimationDuration
                       options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
                    animations:animationBlock
                    completion:NULL];
}
- (void)_setTouchHighlighted:(BOOL)highlighted {
    float alphaValue = highlighted ? _highlightAlpha : _standbyAlpha;
    if (self.homeButtonView.alpha == alphaValue)
        return;
    if (_animatedHighlighting) {
        [self _animateWithBlock:^{
            if (self.homeButtonView != nil) {
                self.homeButtonView.alpha = alphaValue;
            }
        }];
    } else {
        if (self.homeButtonView != nil) {
            self.homeButtonView.alpha = alphaValue;
        }
    }
}
- (float)_combinedButtonHeight {
    float height = 0;
    for (UIButton *button in _buttonContainer) {
        height += button.frame.size.height + self.buttonSpacing;
    }
    return height;
}
- (float)_combinedButtonWidth {
    float width = 0;
    for (UIButton *button in _buttonContainer) {
        width += button.frame.size.width + self.buttonSpacing;
    }
    return width;
}
- (void)_prepareForButtonExpansion {
    float buttonHeight = [self _combinedButtonHeight];
    float buttonWidth = [self _combinedButtonWidth];
    switch (self.direction) {
        case DirectionUp:
        {
            self.homeButtonView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
            CGRect frame = self.frame;
            frame.origin.y -= buttonHeight;
            frame.size.height += buttonHeight;
            self.frame = frame;
        }
            break;
        case DirectionDown:
        {
            self.homeButtonView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin;
            CGRect frame = self.frame;
            frame.size.height += buttonHeight;
            self.frame = frame;
        }
            break;
        case DirectionLeft:
        {
            self.homeButtonView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
            CGRect frame = self.frame;
            frame.origin.x -= buttonWidth;
            frame.size.width += buttonWidth;
            self.frame = frame;
        }
            break;
        case DirectionRight:
        {
            self.homeButtonView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
            CGRect frame = self.frame;
            frame.size.width += buttonWidth;
            self.frame = frame;
        }
            break;
        default:
            break;
    }
}
- (void)_finishCollapse {
    self.frame = _originFrame;
}
- (UIView *)_subviewForPoint:(CGPoint)point {
    for (UIView *subview in self.subviews) {
        if (CGRectContainsPoint(subview.frame, point)) {
            return subview;
        }
    }
    return self;
}
- (NSArray *)_reverseOrderFromArray:(NSArray *)array {
    NSMutableArray *reverseArray = [NSMutableArray array];
    for (int i = (int)array.count - 1; i >= 0; i--) {
        [reverseArray addObject:[array objectAtIndex:i]];
    }
    return reverseArray;
}
#pragma mark -
#pragma mark Setters/Getters
- (void)setHomeButtonView:(UIView *)homeButtonView {
    if (_homeButtonView != homeButtonView) {
        _homeButtonView = homeButtonView;
    }
    if ([_homeButtonView isDescendantOfView:self] == NO) {
        [self addSubview:_homeButtonView];
    }
}
- (NSArray *)buttons {
    return [_buttonContainer copy];
}
#pragma mark -
#pragma mark Touch Handling Methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    if (CGRectContainsPoint(self.homeButtonView.frame, [touch locationInView:self])) {
        [self _setTouchHighlighted:YES];
    }
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    [self _setTouchHighlighted:NO];
    if (CGRectContainsPoint(self.homeButtonView.frame, [touch locationInView:self])) {
        if (_isCollapsed) {
            [self showButtons];
        } else {
            [self dismissButtons];
        }
    }
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    [self _setTouchHighlighted:NO];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesMoved:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    [self _setTouchHighlighted:CGRectContainsPoint(self.homeButtonView.frame, [touch locationInView:self])];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self) {
        if (_isCollapsed) {
            return self;
        } else {
            return [self _subviewForPoint:point];
        }
    }
    return hitView;
}
#pragma mark -
#pragma mark UIGestureRecognizer Delegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch {
    CGPoint touchLocation = [touch locationInView:self];
    if ([self _subviewForPoint:touchLocation] != self && _collapseAfterSelection) {
        return YES;
    }
    return NO;
}
#pragma mark -
#pragma mark Lifecycle
- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self _defaultInit];
    }
    return self;
}
- (id)initWithFrame:(CGRect)frame expansionDirection:(ExpansionDirection)direction {
    self = [super initWithFrame:frame];
    if (self) {
        [self _defaultInit];
        _direction = direction;
    }
    return self;
}
@end