├── Clock.playground ├── contents.xcplayground ├── section-1.swift └── timeline.xctimeline ├── README.md └── screenshot.png /Clock.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Clock.playground/section-1.swift: -------------------------------------------------------------------------------- 1 | // Clockground - noun: a place where people can clock 2 | 3 | import Cocoa 4 | import QuartzCore 5 | import XCPlayground 6 | 7 | class ClockView: NSView { 8 | let backgroundLayer = CAShapeLayer() 9 | let faceLayer = CAShapeLayer() 10 | let handsLayer = CAShapeLayer() 11 | 12 | let clockSize = CGFloat(256.0) 13 | 14 | override init() { 15 | super.init(frame: NSRect(x:0, y:0, width:clockSize, height:clockSize)) 16 | 17 | self.backgroundLayer.frame = self.bounds 18 | self.faceLayer.frame = self.bounds 19 | self.handsLayer.frame = self.bounds 20 | 21 | setUpBackgroundLayer() 22 | setUpFaceLayer() 23 | setUpHandsLayer() 24 | 25 | // this is required to use layers in NSView, 26 | // different from default UIView behavior! 27 | self.wantsLayer = true 28 | 29 | self.layer?.addSublayer(self.backgroundLayer) 30 | self.layer?.addSublayer(self.faceLayer) 31 | self.layer?.addSublayer(self.handsLayer) 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | func setUpBackgroundLayer() { 39 | let backgroundPath = NSBezierPath(roundedRect: self.bounds, xRadius: 45, yRadius: 45) 40 | 41 | self.backgroundLayer.path = CGPathFromNSBezierPath(backgroundPath) 42 | } 43 | 44 | func setUpFaceLayer() { 45 | let faceInset = CGFloat(15.0) 46 | let faceRadius = clockSize-(2*faceInset) 47 | let numberRadius = CGFloat(90.0) 48 | let numberWidth = CGFloat(22.0) 49 | let numberHeight = CGFloat(22.0) 50 | 51 | let facePath = NSBezierPath(ovalInRect: NSRect(x:faceInset, y:faceInset, width:faceRadius, height:faceRadius)) 52 | 53 | self.faceLayer.fillColor = NSColor.whiteColor().CGColor 54 | self.faceLayer.path = CGPathFromNSBezierPath(facePath) 55 | 56 | for hour in 1...12 { 57 | let hourtext = NSString(format: "%d", hour) 58 | 59 | let pct = Double(hour)/12 60 | let position = (pct*2*M_PI)-(M_PI_2) 61 | 62 | // XCPCaptureValue("sinvalue", sin(Double(position))) 63 | let xadj = CGFloat(cos(Double(position))) 64 | let yadj = CGFloat(sin(Double(position))) 65 | let xpos = (clockSize/2) + (xadj*numberRadius) - (numberWidth/2) 66 | let ypos = clockSize - ((clockSize/2) + (yadj*numberRadius) + (numberWidth/2)) 67 | 68 | let text = CATextLayer() 69 | text.frame = CGRectMake(xpos, ypos, numberWidth, numberHeight) 70 | text.string = hourtext 71 | text.fontSize = 20 72 | text.font = NSFont(name: "HelveticaNeue-Light", size: 10) 73 | text.alignmentMode = kCAAlignmentCenter 74 | text.foregroundColor = NSColor.blackColor().CGColor 75 | 76 | self.faceLayer.addSublayer(text) 77 | } 78 | } 79 | 80 | func setUpHandsLayer() { 81 | // look up time for hand values 82 | let format = NSDateFormatter() 83 | let now = NSDate() 84 | 85 | format.dateFormat = "hh" 86 | let hour = format.stringFromDate(now).toInt()! 87 | let hourRotation = CGFloat((2.0*M_PI*(Double(hour)/12.0))+M_PI) 88 | 89 | format.dateFormat = "mm" 90 | let minute = format.stringFromDate(now).toInt()! 91 | let minuteRotation = CGFloat((2.0*M_PI*(Double(minute)/60.0))+M_PI) 92 | 93 | format.dateFormat = "ss" 94 | let second = format.stringFromDate(now).toInt()! 95 | let secondRotation = CGFloat((2.0*M_PI*(Double(second)/60.0))+M_PI) 96 | 97 | // style setup for the hands 98 | let darkCenter = CAShapeLayer() 99 | let darkSize = CGFloat(13.0) 100 | let redCenter = CAShapeLayer() 101 | redCenter.fillColor = NSColor.redColor().CGColor 102 | let redSize = CGFloat(5.0) 103 | 104 | // draw the black center circle 105 | let darkPath = NSBezierPath(ovalInRect: CGRectMake((clockSize/2)-(darkSize/2), (clockSize/2)-(darkSize/2), darkSize, darkSize)) 106 | darkCenter.path = CGPathFromNSBezierPath(darkPath) 107 | 108 | self.handsLayer.addSublayer(darkCenter) 109 | 110 | // set up the minute hand 111 | let minuteWidth = CGFloat(3.0) 112 | let minuteHeight = CGFloat(75.0) 113 | let minuteStartPoint = CGPointMake((clockSize/2)-(minuteWidth/2), clockSize/2) 114 | 115 | // draw minute hand 116 | let minuteHand = CAShapeLayer() 117 | minuteHand.frame = CGRectMake(0, 0, clockSize, clockSize) 118 | minuteHand.fillColor = NSColor.blackColor().CGColor 119 | 120 | minuteHand.path = self.makeRectPath(minuteStartPoint, width: minuteWidth, height: minuteHeight) 121 | 122 | // anchor point is already 0.5,0.5 so we can just rotate 123 | minuteHand.transform = CATransform3DMakeRotation(minuteRotation, 0, 0, -1) 124 | 125 | self.handsLayer.addSublayer(minuteHand) 126 | 127 | // set up the hour hand 128 | let hourWidth = CGFloat(4.0) 129 | let hourHeight = CGFloat(35.0) 130 | let hourStartPoint = CGPointMake((clockSize/2)-(hourWidth/2), clockSize/2) 131 | 132 | // draw hour hand 133 | let hourHand = CAShapeLayer() 134 | hourHand.frame = CGRectMake(0, 0, clockSize, clockSize) 135 | hourHand.fillColor = NSColor.blackColor().CGColor 136 | 137 | hourHand.path = self.makeRectPath(hourStartPoint, width: hourWidth, height: hourHeight) 138 | 139 | // anchor point is already 0.5,0.5 so we can just rotate 140 | hourHand.transform = CATransform3DMakeRotation(hourRotation, 0, 0, -1) 141 | 142 | self.handsLayer.addSublayer(hourHand) 143 | 144 | // draw the red center bit over the black existing ones 145 | let redPath = NSBezierPath(ovalInRect: CGRectMake((clockSize/2)-(redSize/2), (clockSize/2)-(redSize/2), redSize, redSize)) 146 | redCenter.path = CGPathFromNSBezierPath(redPath) 147 | 148 | self.handsLayer.addSublayer(redCenter) 149 | 150 | // set up the seconds hand 151 | let secondWidth = CGFloat(2.0) 152 | let secondHeight = CGFloat(65.0) 153 | let secondStartPoint = CGPointMake((clockSize/2)-(secondWidth/2), clockSize/2) 154 | 155 | // draw the seconds hand 156 | let secondHand = CAShapeLayer() 157 | secondHand.frame = CGRectMake(0, 0, clockSize, clockSize) 158 | secondHand.fillColor = NSColor.redColor().CGColor 159 | 160 | secondHand.path = self.makeRectPath(secondStartPoint, width: secondWidth, height: secondHeight) 161 | 162 | // anchor point is already 0.5,0.5 so we can just rotate 163 | secondHand.transform = CATransform3DMakeRotation(secondRotation, 0, 0, -1) 164 | 165 | // rotate half a circle in thirty seconds 166 | var rotationAnimation = CABasicAnimation(keyPath: "transform") 167 | rotationAnimation.duration = 30 168 | rotationAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 169 | // this tiny amount off ensures that we always rotate in the 170 | // correct direction around the circle 171 | rotationAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeRotation(secondRotation+CGFloat(M_PI)-(0.001), 0, 0, -1)) 172 | 173 | secondHand.addAnimation(rotationAnimation, forKey: "rotationTransform") 174 | 175 | self.handsLayer.addSublayer(secondHand) 176 | } 177 | 178 | // convenience, makes a rectangular path 179 | func makeRectPath(start: CGPoint, width: CGFloat, height: CGFloat) -> CGPath { 180 | 181 | let path = CGPathCreateMutable() 182 | CGPathMoveToPoint(path, nil, start.x, start.y) 183 | CGPathAddLineToPoint(path, nil, start.x, start.y - height) 184 | CGPathAddLineToPoint(path, nil, start.x+width, start.y-height) 185 | CGPathAddLineToPoint(path, nil, start.x+width, start.y) 186 | CGPathCloseSubpath(path) 187 | 188 | return path 189 | } 190 | } 191 | 192 | // thanks WWDC session 408 :) 193 | func CGPathFromNSBezierPath(nspath: NSBezierPath) -> CGPath? { 194 | if nspath.elementCount == 0 { 195 | return nil 196 | } 197 | 198 | let path = CGPathCreateMutable() 199 | var didClosePath = false 200 | 201 | for i in 0.. 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | swiftclock 2 | ========== 3 | 4 | ![timeline view of the clock](https://raw.githubusercontent.com/nickoneill/swiftclock/master/screenshot.png) 5 | 6 | An iOS Clock icon in a swift playground. It reads the current time and animates the second hand for 30 seconds in the style of the iOS clock icon. 7 | 8 | Compatible with Xcode 6.1 thanks to [Corinne Krych](https://github.com/corinnekrych) 9 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoneill/swiftclock/20131e538940ac15c994eb27276aad9dc1906794/screenshot.png --------------------------------------------------------------------------------