Instantly share code, notes, and snippets.
Forked from raphaelschaad/RSPlayPauseButton.h
Created
March 24, 2014 10:28
-
Star
(1)
1
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save matthewryan/9737807 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// RSPlayPauseButton.h | |
// | |
// Created by Raphael Schaad on 2014-03-22. | |
// This is free and unencumbered software released into the public domain. | |
// | |
#import <UIKit/UIKit.h> | |
// | |
// Displays a ⃝ with either the ► (play) or ❚❚ (pause) icon and nicely morphs between the two states. | |
// It sends an action message to a target when tapped like `UIButton`. | |
// | |
@interface RSPlayPauseButton : UIControl | |
// State | |
@property (nonatomic, assign, getter = isPaused) BOOL paused; // Default is `YES` | |
// Style | |
@property (nonatomic, strong) UIColor *color; // Default is 90% black | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// RSPlayPauseButton.m | |
// | |
// Created by Raphael Schaad on 2014-03-22. | |
// This is free and unencumbered software released into the public domain. | |
// | |
#import "RSPlayPauseButton.h" | |
#include <tgmath.h> // type generic math, yo: http://en.wikipedia.org/wiki/Tgmath.h#tgmath.h | |
static const CGFloat kScale = 1.0; | |
static const CGFloat kBorderSize = 32.0 * kScale; | |
static const CGFloat kBorderWidth = 3.0 * kScale; | |
static const CGFloat kSize = kBorderSize + kBorderWidth; // The total size is the border size + 2x half the border width. | |
static const CGFloat kPauseLineWidth = 4.0 * kScale; | |
static const CGFloat kPauseLineHeight = 15.0 * kScale; | |
static const CGFloat kPauseLinesSpace = 4.0 * kScale; | |
static const CGFloat kPlayTriangleOffsetX = 1.0 * kScale; | |
static const CGFloat kPlayTriangleTipOffsetX = 2.0 * kScale; | |
static const CGPoint p1 = {0.0, 0.0}; // line 1, top left | |
static const CGPoint p2 = {kPauseLineWidth, 0.0}; // line 1, top right | |
static const CGPoint p3 = {kPauseLineWidth, kPauseLineHeight}; // line 1, bottom right | |
static const CGPoint p4 = {0.0, kPauseLineHeight}; // line 1, bottom left | |
static const CGPoint p5 = {kPauseLineWidth + kPauseLinesSpace, 0.0}; // line 2, top left | |
static const CGPoint p6 = {kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth, 0.0}; // line 2, top right | |
static const CGPoint p7 = {kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth, kPauseLineHeight}; // line 2, bottom right | |
static const CGPoint p8 = {kPauseLineWidth + kPauseLinesSpace, kPauseLineHeight}; // line 2, bottom left | |
@interface RSPlayPauseButton () | |
@property (nonatomic, strong) CAShapeLayer *borderShapeLayer; | |
@property (nonatomic, strong) CAShapeLayer *playPauseShapeLayer; | |
@property (nonatomic, strong, readonly) UIBezierPath *pauseBezierPath; | |
@property (nonatomic, strong, readonly) UIBezierPath *playBezierPath; | |
@end | |
@implementation RSPlayPauseButton | |
#pragma mark - Accessors | |
@synthesize pauseBezierPath = _pauseBezierPath; | |
- (UIBezierPath *)pauseBezierPath | |
{ | |
if (!_pauseBezierPath) { | |
_pauseBezierPath = [UIBezierPath bezierPath]; | |
// Subpath for 1. line | |
[_pauseBezierPath moveToPoint:p1]; | |
[_pauseBezierPath addLineToPoint:p2]; | |
[_pauseBezierPath addLineToPoint:p3]; | |
[_pauseBezierPath addLineToPoint:p4]; | |
[_pauseBezierPath closePath]; | |
// Subpath for 2. line | |
[_pauseBezierPath moveToPoint:p5]; | |
[_pauseBezierPath addLineToPoint:p6]; | |
[_pauseBezierPath addLineToPoint:p7]; | |
[_pauseBezierPath addLineToPoint:p8]; | |
[_pauseBezierPath closePath]; | |
} | |
return _pauseBezierPath; | |
} | |
@synthesize playBezierPath = _playBezierPath; | |
- (UIBezierPath *)playBezierPath | |
{ | |
if (!_playBezierPath) { | |
_playBezierPath = [UIBezierPath bezierPath]; | |
const CGFloat kPauseLinesHalfSpace = floor(kPauseLinesSpace / 2); | |
const CGFloat kPauseLineHalfHeight = floor(kPauseLineHeight / 2); | |
CGPoint _p1 = CGPointMake(p1.x + kPlayTriangleOffsetX, p1.y); | |
CGPoint _p2 = CGPointMake(p2.x + kPauseLinesHalfSpace, p2.y); | |
CGPoint _p3 = CGPointMake(p3.x + kPauseLinesHalfSpace, p3.y); | |
CGPoint _p4 = CGPointMake(p4.x + kPlayTriangleOffsetX, p4.y); | |
CGPoint _p5 = CGPointMake(p5.x - kPauseLinesHalfSpace, p5.y); | |
CGPoint _p6 = CGPointMake(p6.x + kPlayTriangleTipOffsetX, p6.y); | |
CGPoint _p7 = CGPointMake(p7.x + kPlayTriangleTipOffsetX, p7.y); | |
CGPoint _p8 = CGPointMake(p8.x - kPauseLinesHalfSpace, p8.y); | |
const CGFloat kPlayTriangleWidth = _p6.x - _p1.x; | |
_p2.y += kPauseLineHalfHeight * (_p2.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; | |
_p3.y -= kPauseLineHalfHeight * (_p3.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; | |
_p5.y += kPauseLineHalfHeight * (_p5.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; | |
_p6.y = kPauseLineHalfHeight; | |
_p7.y = kPauseLineHalfHeight; | |
_p8.y -= kPauseLineHalfHeight * (_p8.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; | |
[_playBezierPath moveToPoint:_p1]; | |
[_playBezierPath addLineToPoint:_p2]; | |
[_playBezierPath addLineToPoint:_p3]; | |
[_playBezierPath addLineToPoint:_p4]; | |
[_playBezierPath closePath]; | |
[_playBezierPath moveToPoint:_p5]; | |
[_playBezierPath addLineToPoint:_p6]; | |
[_playBezierPath addLineToPoint:_p7]; | |
[_playBezierPath addLineToPoint:_p8]; | |
[_playBezierPath closePath]; | |
} | |
return _playBezierPath; | |
} | |
#pragma mark - Life Cycle | |
- (id)initWithFrame:(CGRect)frame | |
{ | |
self = [super initWithFrame:frame]; | |
if (self) { | |
self.paused = YES; | |
self.color = [UIColor colorWithWhite:0.1 alpha:1.0]; | |
[self sizeToFit]; | |
} | |
return self; | |
} | |
#pragma mark - UIView Method Overrides | |
#pragma mark Configuring the Resizing Behavior | |
- (CGSize)sizeThatFits:(CGSize)size | |
{ | |
CGSize sizeThatFits = [super sizeThatFits:size]; | |
// Ignore the current size/new size by super, and instead use our default size. | |
sizeThatFits = CGSizeMake(kSize, kSize); | |
return sizeThatFits; | |
} | |
#pragma mark Laying out Subviews | |
- (void)layoutSubviews | |
{ | |
[super layoutSubviews]; | |
if (!self.borderShapeLayer) { | |
self.borderShapeLayer = [[CAShapeLayer alloc] init]; | |
// Adjust for line width | |
CGRect borderRect = CGRectInset(self.bounds, ceil(kBorderWidth / 2), ceil(kBorderWidth / 2)); | |
self.borderShapeLayer.path = [UIBezierPath bezierPathWithOvalInRect:borderRect].CGPath; | |
self.borderShapeLayer.lineWidth = kBorderWidth; | |
self.borderShapeLayer.strokeColor = self.color.CGColor; | |
self.borderShapeLayer.fillColor = [UIColor clearColor].CGColor; | |
[self.layer addSublayer:self.borderShapeLayer]; | |
} | |
if (!self.playPauseShapeLayer) { | |
self.playPauseShapeLayer = [[CAShapeLayer alloc] init]; | |
CGRect playPauseRect = CGRectZero; | |
playPauseRect.origin.x = floor(((self.bounds.size.width) - (kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth)) / 2); | |
playPauseRect.origin.y = floor(((self.bounds.size.height) - (kPauseLineHeight)) / 2); | |
playPauseRect.size.width = kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth + kPlayTriangleTipOffsetX; | |
playPauseRect.size.height = kPauseLineHeight; | |
self.playPauseShapeLayer.frame = playPauseRect; | |
self.playPauseShapeLayer.fillColor = self.color.CGColor; | |
UIBezierPath *path = self.isPaused ? self.playBezierPath : self.pauseBezierPath; | |
self.playPauseShapeLayer.path = path.CGPath; | |
[self.layer addSublayer:self.playPauseShapeLayer]; | |
} | |
} | |
#pragma mark - UIControl Method Overrides | |
#pragma mark Preparing and Sending Action Messages | |
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event | |
{ | |
// Update the state | |
self.paused = !self.isPaused; | |
// Morph between the two states | |
UIBezierPath *fromPath = self.isPaused ? self.pauseBezierPath : self.playBezierPath; | |
UIBezierPath *toPath = self.isPaused ? self.playBezierPath : self.pauseBezierPath; | |
CABasicAnimation *morphAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; | |
CAMediaTimingFunction *timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; | |
[morphAnimation setTimingFunction:timingFunction]; | |
// Make the new state stick | |
[morphAnimation setRemovedOnCompletion:NO]; | |
[morphAnimation setFillMode:kCAFillModeForwards]; | |
morphAnimation.duration = 0.3; | |
morphAnimation.fromValue = (__bridge id)fromPath.CGPath; | |
morphAnimation.toValue = (__bridge id)toPath.CGPath; | |
[self.playPauseShapeLayer addAnimation:morphAnimation forKey:nil]; | |
// Forward the action | |
[super sendAction:action to:target forEvent:event]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment