Instantly share code, notes, and snippets.
Last active
August 25, 2016 22:41
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save toddhopkinson/893fff926f3f1a549c08f41acd05a5ed to your computer and use it in GitHub Desktop.
Custom UIButton Drawing UIBezierPaths to CAShapeLayers and morph-animating them via CABasicAnimation
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
// CameraButton.swift | |
// Created by Todd Hopkinson Aug 2016 | |
// UIButton approximating Apple's camera button appearance and appearance behavior | |
import UIKit | |
enum CameraButtonMode { | |
case VideoReady | |
case VideoRecording | |
case PhotoReady | |
} | |
class CameraButton: UIButton { | |
var operationMode: CameraButtonMode = CameraButtonMode.PhotoReady | |
private var diskLayer: CAShapeLayer! | |
private var ringLayer: CAShapeLayer! | |
private var roundRectCGPath: CGPath! | |
private var diskCGPath: CGPath! | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupPaths() | |
} | |
required init(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder)! | |
setupPaths() | |
} | |
private func setupPaths() { | |
let centerPoint = CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0) | |
let diskPath = circlePathWithCenter(centerPoint, radius: (frame.size.width - 18)/2) | |
diskCGPath = diskPath.CGPath | |
let ringPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true) | |
let roundRectPath = squarePathWithCenter(centerPoint, side: centerPoint.x/1.6) | |
roundRectCGPath = roundRectPath.CGPath | |
// Setup the ring layer | |
ringLayer = CAShapeLayer() | |
ringLayer.path = ringPath.CGPath | |
ringLayer.lineWidth = 4.0 | |
ringLayer.strokeColor = UIColor.whiteColor().colorWithAlphaComponent(0.75).CGColor | |
ringLayer.fillColor = UIColor.clearColor().CGColor | |
ringLayer.strokeEnd = 1.0 | |
layer.addSublayer(ringLayer) | |
// Setup the disk layer | |
diskLayer = CAShapeLayer() | |
diskLayer.path = diskPath.CGPath | |
diskLayer.fillColor = UIColor.whiteColor().CGColor | |
diskLayer.strokeColor = UIColor.clearColor().CGColor | |
diskLayer.strokeEnd = 1.0 | |
// Add the diskLayer to the view's layer's sublayers | |
layer.addSublayer(diskLayer) | |
} | |
private func circlePathWithCenter(center: CGPoint, radius: CGFloat) -> UIBezierPath { | |
let circlePath = UIBezierPath() | |
circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI), endAngle: -CGFloat(M_PI/2), clockwise: true) | |
circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI/2), endAngle: 0, clockwise: true) | |
circlePath.addArcWithCenter(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI/2), clockwise: true) | |
circlePath.addArcWithCenter(center, radius: radius, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(M_PI), clockwise: true) | |
circlePath.closePath() | |
return circlePath | |
} | |
// TODO: make this square into a roundRect | |
private func squarePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath { | |
let squarePath = UIBezierPath() | |
let startX = center.x - side / 2 | |
let startY = center.y - side / 2 | |
squarePath.moveToPoint(CGPoint(x: startX, y: startY)) | |
squarePath.addLineToPoint(squarePath.currentPoint) | |
squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY)) | |
squarePath.addLineToPoint(squarePath.currentPoint) | |
squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY + side)) | |
squarePath.addLineToPoint(squarePath.currentPoint) | |
squarePath.addLineToPoint(CGPoint(x: startX, y: startY + side)) | |
squarePath.addLineToPoint(squarePath.currentPoint) | |
squarePath.closePath() | |
return squarePath | |
} | |
private func morphDiskToRoundRect() { | |
let morph: CABasicAnimation = CABasicAnimation(keyPath: "path") | |
morph.duration = 0.33 | |
morph.fromValue = diskCGPath | |
morph.toValue = roundRectCGPath | |
morph.removedOnCompletion = false | |
morph.fillMode = kCAFillModeForwards | |
diskLayer.addAnimation(morph, forKey: "morph disk to roundrect") | |
} | |
private func morphRoundRectToDisk() { | |
let morph: CABasicAnimation = CABasicAnimation(keyPath: "path") | |
morph.duration = 0.33 | |
morph.fromValue = roundRectCGPath | |
morph.toValue = diskCGPath | |
morph.removedOnCompletion = false | |
morph.fillMode = kCAFillModeForwards | |
diskLayer.addAnimation(morph, forKey: "morph roundrect to disk") | |
} | |
func updateButtonMode(mode: CameraButtonMode) { | |
let previousMode = self.operationMode | |
self.operationMode = mode | |
switch mode { | |
case .PhotoReady: | |
// change disk color to white | |
self.diskLayer.path = diskCGPath | |
self.diskLayer.fillColor = UIColor.whiteColor().CGColor | |
if previousMode == .VideoRecording { | |
self.morphRoundRectToDisk() | |
} | |
break | |
case .VideoReady: | |
self.diskLayer.fillColor = UIColor.redColor().CGColor | |
if previousMode == .VideoRecording { | |
self.morphRoundRectToDisk() | |
} else { | |
self.diskLayer.path = diskCGPath | |
} | |
break | |
case .VideoRecording: | |
self.morphDiskToRoundRect() | |
break | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment