Skip to content

Instantly share code, notes, and snippets.

@toddhopkinson
Last active August 25, 2016 22:41
Show Gist options
  • Save toddhopkinson/893fff926f3f1a549c08f41acd05a5ed to your computer and use it in GitHub Desktop.
Save toddhopkinson/893fff926f3f1a549c08f41acd05a5ed to your computer and use it in GitHub Desktop.
Custom UIButton Drawing UIBezierPaths to CAShapeLayers and morph-animating them via CABasicAnimation
// 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