Last active
September 20, 2016 20:06
-
-
Save mobyjames/766acc218deb4437d5b7239a4f71cc77 to your computer and use it in GitHub Desktop.
Broken Clock
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
import UIKit | |
import QuartzCore | |
import XCPlayground | |
import PlaygroundSupport | |
PlaygroundPage.current.needsIndefiniteExecution = true | |
class ClockView: UIView { | |
let backgroundLayer = CAShapeLayer() | |
let faceLayer = CAShapeLayer() | |
let handsLayer = CAShapeLayer() | |
let clockSize = CGFloat(256.0) | |
init() { | |
super.init(frame: CGRect(x:0, y:0, width:clockSize, height:clockSize)) | |
self.backgroundLayer.frame = self.bounds | |
self.faceLayer.frame = self.bounds | |
self.handsLayer.frame = self.bounds | |
setUpBackgroundLayer() | |
setUpFaceLayer() | |
setUpHandsLayer() | |
self.layer.addSublayer(self.backgroundLayer) | |
self.layer.addSublayer(self.faceLayer) | |
self.layer.addSublayer(self.handsLayer) | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
func setUpBackgroundLayer() { | |
let backgroundPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 45) | |
self.backgroundLayer.path = backgroundPath.cgPath | |
} | |
func setUpFaceLayer() { | |
let faceInset = CGFloat(15.0) | |
let faceRadius = clockSize-(2*faceInset) | |
let numberRadius = CGFloat(90.0) | |
let numberWidth = CGFloat(22.0) | |
let numberHeight = CGFloat(22.0) | |
let facePath = UIBezierPath(ovalIn: CGRect(x:faceInset, y:faceInset, width:faceRadius, height:faceRadius)) | |
self.faceLayer.fillColor = UIColor.white.cgColor | |
self.faceLayer.path = facePath.cgPath | |
for hour in 1...12 { | |
let hourtext = NSString(format: "%d", hour) | |
let pct = Double(hour)/12 | |
let position = (pct*2*M_PI)+(M_PI_2) | |
let xadj = CGFloat(cos(Double(position))) | |
let yadj = CGFloat(sin(Double(position))) | |
let xpos = (clockSize/2) + (xadj*numberRadius) - (numberWidth/2) | |
let ypos = clockSize - ((clockSize/2) + (yadj*numberRadius) + (numberWidth/2)) | |
let text = CATextLayer() | |
text.frame = CGRect(x:xpos, y:ypos, width:numberWidth, height:numberHeight) | |
text.string = hourtext | |
text.fontSize = 20 | |
text.font = UIFont(name: "HelveticaNeue-Light", size: 10) | |
text.alignmentMode = kCAAlignmentCenter | |
text.foregroundColor = UIColor.black.cgColor | |
self.faceLayer.addSublayer(text) | |
} | |
} | |
func setUpHandsLayer() { | |
// look up time for hand values | |
let format = DateFormatter() | |
let now = NSDate() | |
format.dateFormat = "hh" | |
let hour = Int(format.string(from: now as Date))! | |
let hourRotation = CGFloat((2.0*M_PI*(Double(hour)/12.0))+M_PI) | |
format.dateFormat = "mm" | |
let minute = Int(format.string(from: now as Date))! | |
let minuteRotation = CGFloat((2.0*M_PI*(Double(minute)/60.0))+M_PI) | |
format.dateFormat = "ss" | |
let second = Int(format.string(from: now as Date))! | |
let secondRotation = CGFloat((2.0*M_PI*(Double(second)/60.0))+M_PI) | |
// style setup for the hands | |
let darkCenter = CAShapeLayer() | |
let darkSize = CGFloat(13.0) | |
let redCenter = CAShapeLayer() | |
redCenter.fillColor = UIColor.red.cgColor | |
let redSize = CGFloat(5.0) | |
// draw the black center circle | |
let darkPath = UIBezierPath(ovalIn: CGRect(x:(clockSize/2)-(darkSize/2), y:(clockSize/2)-(darkSize/2), width:darkSize, height:darkSize)) | |
darkCenter.path = darkPath.cgPath | |
self.handsLayer.addSublayer(darkCenter) | |
// set up the minute hand | |
let minuteWidth = CGFloat(3.0) | |
let minuteHeight = CGFloat(75.0) | |
let minuteStartPoint = CGPoint(x:(clockSize/2)-(minuteWidth/2), y:clockSize/2) | |
// draw minute hand | |
let minuteHand = CAShapeLayer() | |
minuteHand.frame = CGRect(x:0, y:0, width:clockSize, height:clockSize) | |
minuteHand.fillColor = UIColor.black.cgColor | |
minuteHand.path = self.makeRectPath(start: minuteStartPoint, width: minuteWidth, height: minuteHeight) | |
// anchor point is already 0.5,0.5 so we can just rotate | |
minuteHand.transform = CATransform3DMakeRotation(minuteRotation, 0, 0, -1) | |
self.handsLayer.addSublayer(minuteHand) | |
// set up the hour hand | |
let hourWidth = CGFloat(4.0) | |
let hourHeight = CGFloat(35.0) | |
let hourStartPoint = CGPoint(x:(clockSize/2)-(hourWidth/2), y:clockSize/2) | |
// draw hour hand | |
let hourHand = CAShapeLayer() | |
hourHand.frame = CGRect(x:0, y:0, width:clockSize, height:clockSize) | |
hourHand.fillColor = UIColor.black.cgColor | |
hourHand.path = self.makeRectPath(start: hourStartPoint, width: hourWidth, height: hourHeight) | |
// anchor point is already 0.5,0.5 so we can just rotate | |
hourHand.transform = CATransform3DMakeRotation(hourRotation, 0, 0, -1) | |
self.handsLayer.addSublayer(hourHand) | |
// draw the red center bit over the black existing ones | |
let redPath = UIBezierPath(ovalIn: CGRect(x:(clockSize/2)-(redSize/2), y:(clockSize/2)-(redSize/2), width:redSize, height:redSize)) | |
redCenter.path = redPath.cgPath | |
self.handsLayer.addSublayer(redCenter) | |
// set up the seconds hand | |
let secondWidth = CGFloat(2.0) | |
let secondHeight = CGFloat(65.0) | |
let secondStartPoint = CGPoint(x:(clockSize/2)-(secondWidth/2), y:clockSize/2) | |
// draw the seconds hand | |
let secondHand = CAShapeLayer() | |
secondHand.frame = CGRect(x:0, y:0, width:clockSize, height:clockSize) | |
secondHand.fillColor = UIColor.red.cgColor | |
secondHand.path = self.makeRectPath(start: secondStartPoint, width: secondWidth, height: secondHeight) | |
// anchor point is already 0.5,0.5 so we can just rotate | |
secondHand.transform = CATransform3DMakeRotation(secondRotation, 0, 0, -1) | |
// rotate half a circle in thirty seconds | |
let rotationAnimation = CABasicAnimation(keyPath: "transform") | |
rotationAnimation.duration = 30 | |
rotationAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) | |
// this tiny amount off ensures that we always rotate in the | |
// correct direction around the circle | |
rotationAnimation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(secondRotation+CGFloat(M_PI)-(0.001), 0, 0, -1)) | |
secondHand.add(rotationAnimation, forKey: "rotationTransform") | |
self.handsLayer.addSublayer(secondHand) | |
} | |
// convenience, makes a rectangular path | |
func makeRectPath(start: CGPoint, width: CGFloat, height: CGFloat) -> CGPath { | |
let path = CGMutablePath() | |
path.move(to: start) | |
path.addLine(to: CGPoint(x:start.x, y:start.y - height)) | |
path.addLine(to: CGPoint(x:start.x+width, y:start.y-height)) | |
path.addLine(to: CGPoint(x:start.x+width, y:start.y)) | |
path.closeSubpath() | |
return path | |
} | |
} | |
extension CGPath { | |
func forEach( body: @convention(block) (CGPathElement) -> Void) { | |
typealias Body = @convention(block) (CGPathElement) -> Void | |
let callback: @convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in | |
let body = unsafeBitCast(info, to: Body.self) | |
body(element.pointee) | |
} | |
print(MemoryLayout.size(ofValue: body)) | |
let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self) | |
self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self)) | |
} | |
func getPathElementsPoints() -> [CGPoint] { | |
var arrayPoints : [CGPoint]! = [CGPoint]() | |
self.forEach { element in | |
switch (element.type) { | |
case CGPathElementType.moveToPoint: | |
arrayPoints.append(element.points[0]) | |
case .addLineToPoint: | |
arrayPoints.append(element.points[0]) | |
case .addQuadCurveToPoint: | |
arrayPoints.append(element.points[0]) | |
arrayPoints.append(element.points[1]) | |
case .addCurveToPoint: | |
arrayPoints.append(element.points[0]) | |
arrayPoints.append(element.points[1]) | |
arrayPoints.append(element.points[2]) | |
default: break | |
} | |
} | |
return arrayPoints | |
} | |
func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) { | |
var arrayPoints : [CGPoint]! = [CGPoint]() | |
var arrayTypes : [CGPathElementType]! = [CGPathElementType]() | |
self.forEach { element in | |
switch (element.type) { | |
case CGPathElementType.moveToPoint: | |
arrayPoints.append(element.points[0]) | |
arrayTypes.append(element.type) | |
case .addLineToPoint: | |
arrayPoints.append(element.points[0]) | |
arrayTypes.append(element.type) | |
case .addQuadCurveToPoint: | |
arrayPoints.append(element.points[0]) | |
arrayPoints.append(element.points[1]) | |
arrayTypes.append(element.type) | |
arrayTypes.append(element.type) | |
case .addCurveToPoint: | |
arrayPoints.append(element.points[0]) | |
arrayPoints.append(element.points[1]) | |
arrayPoints.append(element.points[2]) | |
arrayTypes.append(element.type) | |
arrayTypes.append(element.type) | |
arrayTypes.append(element.type) | |
default: break | |
} | |
} | |
return (arrayPoints,arrayTypes) | |
} | |
} | |
let clock = ClockView() | |
PlaygroundPage.current.liveView = clock |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment