|
// |
|
// WFVibrantButton.m |
|
// WorkflowUI |
|
// |
|
// Created by Conrad Kramer on 7/28/14. |
|
// Copyright (c) 2014 DeskConnect. All rights reserved. |
|
// |
|
|
|
#import "WFVibrantButton.h" |
|
#import <CoreText/CoreText.h> |
|
|
|
NS_ASSUME_NONNULL_BEGIN |
|
|
|
@interface WFVibrantButton () |
|
|
|
@property (nonatomic, readonly, weak) UIImageView *outlineView; |
|
@property (nonatomic, readonly, strong) CAShapeLayer *outlineLayer; |
|
@property (nonatomic, copy) UIBezierPath *outlinePath; |
|
@property (nonatomic, copy) UIBezierPath *animatingPath; |
|
|
|
@end |
|
|
|
static CGFloat const WFVibrantButtonMargin = 6.0f; |
|
static CGFloat const WFVibrantButtonCornerRadius = 4.0f; |
|
|
|
static NSString * const WFVibrantButtonAnimatingKey = @"WFVibrantButtonAnimating"; |
|
static NSString * const WFVibrantButtonClipKey = @"WFVibrantButtonClip"; |
|
|
|
@implementation WFVibrantButton |
|
|
|
- (instancetype)initWithBlurEffect:(nullable UIBlurEffect *)effect { |
|
self = [super initWithFrame:CGRectZero]; |
|
if (!self) |
|
return nil; |
|
|
|
self.backgroundColor = [UIColor clearColor]; |
|
|
|
UIView *contentView = self; |
|
|
|
if (effect) { |
|
UIVisualEffectView *vibrantView = [[UIVisualEffectView alloc] initWithEffect:[UIVibrancyEffect effectForBlurEffect:effect]]; |
|
vibrantView.userInteractionEnabled = NO; |
|
vibrantView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); |
|
vibrantView.frame = self.bounds; |
|
[self addSubview:vibrantView]; |
|
contentView = vibrantView.contentView; |
|
} |
|
|
|
UILabel *titleLabel = [[UILabel alloc] init]; |
|
titleLabel.userInteractionEnabled = NO; |
|
titleLabel.font = [UIFont boldSystemFontOfSize:14.0f]; |
|
[contentView addSubview:titleLabel]; |
|
_titleLabel = titleLabel; |
|
|
|
CAShapeLayer *outlineLayer = [CAShapeLayer layer]; |
|
outlineLayer.lineWidth = 1.0f; |
|
outlineLayer.strokeColor = [[UIColor blackColor] CGColor]; |
|
outlineLayer.fillColor = [[UIColor clearColor] CGColor]; |
|
_outlineLayer = outlineLayer; |
|
|
|
UIImageView *outlineView = [[UIImageView alloc] init]; |
|
outlineView.userInteractionEnabled = NO; |
|
outlineView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth); |
|
outlineView.frame = self.bounds; |
|
outlineView.layer.mask = outlineLayer; |
|
[contentView insertSubview:outlineView belowSubview:titleLabel]; |
|
_outlineView = outlineView; |
|
|
|
[self tintColorDidChange]; |
|
|
|
return self; |
|
} |
|
|
|
- (void)tintColorDidChange { |
|
[super tintColorDidChange]; |
|
|
|
CGRect bounds = (CGRect){CGPointZero, CGSizeMake(1, 1)}; |
|
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0.0f); |
|
[self.tintColor setFill]; |
|
[[UIBezierPath bezierPathWithRect:bounds] fill]; |
|
self.outlineView.image = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
|
UIGraphicsEndImageContext(); |
|
|
|
self.titleLabel.textColor = self.tintColor; |
|
} |
|
|
|
- (void)setHighlighted:(BOOL)highlighted { |
|
BOOL update = (self.highlighted != highlighted); |
|
[super setHighlighted:highlighted]; |
|
|
|
if (update) { |
|
if (highlighted) { |
|
CGRect bounds = self.outlineView.layer.bounds; |
|
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0.0f); |
|
CGContextRef context = UIGraphicsGetCurrentContext(); |
|
CGContextAddPath(context, self.outlineLayer.path); |
|
CGContextDrawPath(context, kCGPathFillStroke); |
|
CGContextSetBlendMode(context, kCGBlendModeClear); |
|
[self.titleLabel.attributedText drawInRect:self.titleLabel.frame]; |
|
CALayer *maskLayer = [CALayer layer]; |
|
maskLayer.frame = bounds; |
|
maskLayer.contents = (__bridge id)CGBitmapContextCreateImage(context); |
|
self.outlineView.layer.mask = maskLayer; |
|
self.titleLabel.hidden = YES; |
|
UIGraphicsEndImageContext(); |
|
} else { |
|
self.outlineView.layer.mask = self.outlineLayer; |
|
self.titleLabel.hidden = NO; |
|
} |
|
} |
|
|
|
self.outlineLayer.fillColor = (highlighted ? [[UIColor blackColor] CGColor] : [[UIColor clearColor] CGColor]); |
|
} |
|
|
|
- (void)layoutSubviews { |
|
[super layoutSubviews]; |
|
|
|
CGRect bounds = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(1, 1, 1, 1)); |
|
CGFloat diameter = CGRectGetHeight(bounds); |
|
CGRect animatingRect = CGRectMake(CGRectGetWidth(bounds) - diameter, CGRectGetMinY(bounds), diameter, diameter); |
|
CGPoint center = CGPointMake(CGRectGetMidX(animatingRect), CGRectGetMidY(animatingRect)); |
|
|
|
self.animatingPath = [UIBezierPath bezierPathWithRoundedRect:animatingRect cornerRadius:(diameter / 2.0f)]; |
|
self.outlinePath = [UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:WFVibrantButtonCornerRadius]; |
|
|
|
self.outlineLayer.anchorPoint = CGPointMake(center.x / CGRectGetWidth(self.bounds), center.y / CGRectGetHeight(self.bounds)); |
|
self.outlineLayer.frame = self.bounds; |
|
self.outlineLayer.path = (self.animating ? [self.animatingPath CGPath] : [self.outlinePath CGPath]); |
|
|
|
[self.titleLabel sizeToFit]; |
|
self.titleLabel.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); |
|
} |
|
|
|
- (CGSize)intrinsicContentSize { |
|
CGSize size = self.titleLabel.intrinsicContentSize; |
|
size.height += 2.0f * WFVibrantButtonMargin; |
|
size.width += 4.0f * WFVibrantButtonMargin; |
|
return size; |
|
} |
|
|
|
- (void)setAnimating:(BOOL)animating { |
|
if ([self.outlineLayer animationForKey:WFVibrantButtonClipKey]) |
|
return; |
|
|
|
_animating = animating; |
|
|
|
[self tintColorDidChange]; |
|
|
|
CABasicAnimation *shrinkAnimation = [CABasicAnimation animationWithKeyPath:NSStringFromSelector(@selector(path))]; |
|
shrinkAnimation.toValue = (__bridge id)(animating ? [self.animatingPath CGPath] : [self.outlinePath CGPath]); |
|
shrinkAnimation.delegate = self; |
|
shrinkAnimation.duration = 0.25f; |
|
shrinkAnimation.fillMode = kCAFillModeForwards; |
|
shrinkAnimation.removedOnCompletion = NO; |
|
[self.outlineLayer addAnimation:shrinkAnimation forKey:WFVibrantButtonAnimatingKey]; |
|
|
|
CABasicAnimation *clipAnimation = [CABasicAnimation animationWithKeyPath:NSStringFromSelector(@selector(strokeStart))]; |
|
clipAnimation.toValue = @(animating ? 0.075f : 0.0f); |
|
clipAnimation.delegate = self; |
|
clipAnimation.duration = shrinkAnimation.duration; |
|
clipAnimation.fillMode = kCAFillModeForwards; |
|
clipAnimation.removedOnCompletion = NO; |
|
[self.outlineLayer addAnimation:clipAnimation forKey:WFVibrantButtonClipKey]; |
|
|
|
[UIView animateWithDuration:clipAnimation.duration animations:^{ |
|
self.titleLabel.alpha = (animating ? 0.0f : 1.0f); |
|
}]; |
|
} |
|
|
|
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { |
|
if ([anim isKindOfClass:[CABasicAnimation class]]) { |
|
CABasicAnimation *basicAnimation = (CABasicAnimation *)anim; |
|
if ([basicAnimation.keyPath isEqualToString:NSStringFromSelector(@selector(path))]) { |
|
[self.outlineLayer setValue:basicAnimation.toValue forKey:basicAnimation.keyPath]; |
|
|
|
if (self.animating) { |
|
CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; |
|
spinAnimation.fromValue = @0.0f; |
|
spinAnimation.toValue = @(2*M_PI); |
|
spinAnimation.duration = 1.0f; |
|
spinAnimation.repeatCount = HUGE_VALF; |
|
[self.outlineLayer addAnimation:spinAnimation forKey:WFVibrantButtonAnimatingKey]; |
|
} else { |
|
[self.outlineLayer removeAnimationForKey:WFVibrantButtonAnimatingKey]; |
|
} |
|
} else if ([basicAnimation.keyPath isEqualToString:NSStringFromSelector(@selector(strokeStart))]) { |
|
[self.outlineLayer setValue:basicAnimation.toValue forKey:basicAnimation.keyPath]; |
|
[self.outlineLayer removeAnimationForKey:WFVibrantButtonClipKey]; |
|
} |
|
} |
|
} |
|
|
|
#pragma mark - Accessibility |
|
|
|
- (BOOL)isAccessibilityElement { |
|
return YES; |
|
} |
|
|
|
- (UIAccessibilityTraits)accessibilityTraits { |
|
return (UIAccessibilityTraitButton | (self.enabled ? 0 : UIAccessibilityTraitNotEnabled)); |
|
} |
|
|
|
- (nullable NSString *)accessibilityLabel { |
|
return self.titleLabel.text; |
|
} |
|
|
|
@end |
|
|
|
NS_ASSUME_NONNULL_END |