Skip to content

Instantly share code, notes, and snippets.

@mobyjames
Last active September 20, 2016 20:06
Show Gist options
  • Save mobyjames/766acc218deb4437d5b7239a4f71cc77 to your computer and use it in GitHub Desktop.
Save mobyjames/766acc218deb4437d5b7239a4f71cc77 to your computer and use it in GitHub Desktop.
Broken Clock
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