Last active
March 30, 2021 13:47
-
-
Save sketchytech/d0a8909459aea899e67c to your computer and use it in GitHub Desktop.
Swift: Draw a clock face (part 1)
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
// accompanies this blogpost http://sketchytech.blogspot.co.uk/2014/11/swift-how-to-draw-clock-face-using.html | |
import UIKit | |
class ViewController: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// Do any additional setup after loading the view, typically from a nib. | |
let newView = View(frame: CGRect(x: 0, y: 0, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame))) | |
self.view.addSubview(newView) | |
} | |
} | |
func degree2radian(a:CGFloat)->CGFloat { | |
let b = CGFloat(M_PI) * a/180 | |
return b | |
} | |
func circleCircumferencePoints(sides:Int,x:CGFloat,y:CGFloat,radius:CGFloat,adjustment:CGFloat=0)->[CGPoint] { | |
let angle = degree2radian(360/CGFloat(sides)) | |
let cx = x // x origin | |
let cy = y // y origin | |
let r = radius // radius of circle | |
var i = sides | |
var points = [CGPoint]() | |
while points.count <= sides { | |
let xpo = cx - r * cos(angle * CGFloat(i)+degree2radian(adjustment)) | |
let ypo = cy - r * sin(angle * CGFloat(i)+degree2radian(adjustment)) | |
points.append(CGPoint(x: xpo, y: ypo)) | |
i--; | |
} | |
return points | |
} | |
func secondMarkers(#ctx:CGContextRef, #x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #color:UIColor) { | |
// retrieve points | |
let points = circleCircumferencePoints(sides,x,y,radius) | |
// create path | |
let path = CGPathCreateMutable() | |
// determine length of marker as a fraction of the total radius | |
var divider:CGFloat = 1/16 | |
for p in enumerate(points) { | |
if p.index % 5 == 0 { | |
divider = 1/8 | |
} | |
else { | |
divider = 1/16 | |
} | |
let xn = p.element.x + divider*(x-p.element.x) | |
let yn = p.element.y + divider*(y-p.element.y) | |
// build path | |
CGPathMoveToPoint(path, nil, p.element.x, p.element.y) | |
CGPathAddLineToPoint(path, nil, xn, yn) | |
CGPathCloseSubpath(path) | |
// add path to context | |
CGContextAddPath(ctx, path) | |
} | |
// set path color | |
let cgcolor = color.CGColor | |
CGContextSetStrokeColorWithColor(ctx,cgcolor) | |
CGContextSetLineWidth(ctx, 3.0) | |
CGContextStrokePath(ctx) | |
} | |
func drawText(#rect:CGRect, #ctx:CGContextRef, #x:CGFloat, #y:CGFloat, #radius:CGFloat, #sides:Int, #color:UIColor) { | |
// Flip text co-ordinate space, see: http://blog.spacemanlabs.com/2011/08/quick-tip-drawing-core-text-right-side-up/ | |
CGContextTranslateCTM(ctx, 0.0, CGRectGetHeight(rect)) | |
CGContextScaleCTM(ctx, 1.0, -1.0) | |
// dictates on how inset the ring of numbers will be | |
let inset:CGFloat = radius/3.5 | |
// An adjustment of 270 degrees to position numbers correctly | |
let points = circleCircumferencePoints(sides,x,y,radius-inset,adjustment:270) | |
let path = CGPathCreateMutable() | |
// see | |
for p in enumerate(points) { | |
if p.index > 0 { | |
// Font name must be written exactly the same as the system stores it (some names are hyphenated, some aren't) and must exist on the user's device. Otherwise there will be a crash. (In real use checks and fallbacks would be created.) For a list of iOS 7 fonts see here: http://support.apple.com/en-us/ht5878 | |
let aFont = UIFont(name: "DamascusBold", size: radius/5) | |
// create a dictionary of attributes to be applied to the string | |
let attr:CFDictionaryRef = [NSFontAttributeName:aFont!,NSForegroundColorAttributeName:UIColor.whiteColor()] | |
// create the attributed string | |
let text = CFAttributedStringCreate(nil, p.index.description, attr) | |
// create the line of text | |
let line = CTLineCreateWithAttributedString(text) | |
// retrieve the bounds of the text | |
let bounds = CTLineGetBoundsWithOptions(line, CTLineBoundsOptions.UseOpticalBounds) | |
// set the line width to stroke the text with | |
CGContextSetLineWidth(ctx, 1.5) | |
// set the drawing mode to stroke | |
CGContextSetTextDrawingMode(ctx, kCGTextStroke) | |
// Set text position and draw the line into the graphics context, text length and height is adjusted for | |
let xn = p.element.x - bounds.width/2 | |
let yn = p.element.y - bounds.midY | |
CGContextSetTextPosition(ctx, xn, yn) | |
// the line of text is drawn - see https://developer.apple.com/library/ios/DOCUMENTATION/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html | |
// draw the line of text | |
CTLineDraw(line, ctx) | |
} | |
} | |
} | |
class View: UIView { | |
override func drawRect(rect:CGRect) | |
{ | |
// obtain context | |
let ctx = UIGraphicsGetCurrentContext() | |
// decide on radius | |
let rad = CGRectGetWidth(rect)/3.5 | |
let endAngle = CGFloat(2*M_PI) | |
// add the circle to the context | |
CGContextAddArc(ctx, CGRectGetMidX(rect), CGRectGetMidY(rect), rad, 0, endAngle, 1) | |
// set fill color | |
CGContextSetFillColorWithColor(ctx,UIColor.grayColor().CGColor) | |
// set stroke color | |
CGContextSetStrokeColorWithColor(ctx,UIColor.whiteColor().CGColor) | |
// set line width | |
CGContextSetLineWidth(ctx, 4.0) | |
// use to fill and stroke path (see http://stackoverflow.com/questions/13526046/cant-stroke-path-after-filling-it ) | |
// draw the path | |
CGContextDrawPath(ctx, kCGPathFillStroke); | |
secondMarkers(ctx: ctx, x: CGRectGetMidX(rect), y: CGRectGetMidY(rect), radius: rad, sides: 60, color: UIColor.whiteColor()) | |
drawText(rect:rect, ctx: ctx, x: CGRectGetMidX(rect), y: CGRectGetMidY(rect), radius: rad, sides: 12, color: UIColor.whiteColor()) | |
} | |
} |
heres an update for swift 2:
func degree2radian(a:CGFloat)->CGFloat {
let b = CGFloat(M_PI) * a/180
return b
}
func circleCircumferencePoints(sides:Int,x:CGFloat,y:CGFloat,radius:CGFloat,adjustment:CGFloat=0)->[CGPoint] {
let angle = degree2radian(360/CGFloat(sides))
let cx = x // x origin
let cy = y // y origin
let r = radius // radius of circle
var i = sides
var points = [CGPoint]()
while points.count <= sides {
let xpo = cx - r * cos(angle * CGFloat(i)+degree2radian(adjustment))
let ypo = cy - r * sin(angle * CGFloat(i)+degree2radian(adjustment))
points.append(CGPoint(x: xpo, y: ypo))
i -= 1;
}
return points
}
func secondMarkers(ctx:CGContextRef, x:CGFloat, y:CGFloat, radius:CGFloat, sides:Int, color:UIColor) {
// retrieve points
let points = circleCircumferencePoints(sides,x: x,y: y,radius: radius)
// create path
let path = CGPathCreateMutable()
// determine length of marker as a fraction of the total radius
var divider:CGFloat = 1/16
for p in points.enumerate() {
if p.index % 5 == 0 {
divider = 1/8
}
else {
divider = 1/16
}
let xn = p.element.x + divider*(x-p.element.x)
let yn = p.element.y + divider*(y-p.element.y)
// build path
CGPathMoveToPoint(path, nil, p.element.x, p.element.y)
CGPathAddLineToPoint(path, nil, xn, yn)
CGPathCloseSubpath(path)
// add path to context
CGContextAddPath(ctx, path)
}
// set path color
let cgcolor = color.CGColor
CGContextSetStrokeColorWithColor(ctx,cgcolor)
CGContextSetLineWidth(ctx, 3.0)
CGContextStrokePath(ctx)
}
func drawText(rect:CGRect, ctx:CGContextRef, x:CGFloat, y:CGFloat, radius:CGFloat, sides:Int, color:UIColor) {
// Flip text co-ordinate space, see: http://blog.spacemanlabs.com/2011/08/quick-tip-drawing-core-text-right-side-up/
CGContextTranslateCTM(ctx, 0.0, CGRectGetHeight(rect))
CGContextScaleCTM(ctx, 1.0, -1.0)
// dictates on how inset the ring of numbers will be
let inset:CGFloat = radius/3.5
// An adjustment of 270 degrees to position numbers correctly
let points = circleCircumferencePoints(sides,x: x,y: y,radius: radius-inset,adjustment:270)
_ = CGPathCreateMutable()
// see
for p in points.enumerate() {
if p.index > 0 {
// Font name must be written exactly the same as the system stores it (some names are hyphenated, some aren't) and must exist on the user's device. Otherwise there will be a crash. (In real use checks and fallbacks would be created.) For a list of iOS 7 fonts see here: http://support.apple.com/en-us/ht5878
let aFont = UIFont(name: "DamascusBold", size: radius/5)
// create a dictionary of attributes to be applied to the string
let attr:CFDictionaryRef = [NSFontAttributeName:aFont!,NSForegroundColorAttributeName:UIColor.whiteColor()]
// create the attributed string
let text = CFAttributedStringCreate(nil, p.index.description, attr)
// create the line of text
let line = CTLineCreateWithAttributedString(text)
// retrieve the bounds of the text
let bounds = CTLineGetBoundsWithOptions(line, CTLineBoundsOptions.UseOpticalBounds)
// set the line width to stroke the text with
CGContextSetLineWidth(ctx, 1.5)
// set the drawing mode to stroke
CGContextSetTextDrawingMode(ctx, CGTextDrawingMode.FillStroke)
// Set text position and draw the line into the graphics context, text length and height is adjusted for
let xn = p.element.x - bounds.width/2
let yn = p.element.y - bounds.midY
CGContextSetTextPosition(ctx, xn, yn)
// the line of text is drawn - see https://developer.apple.com/library/ios/DOCUMENTATION/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html
// draw the line of text
CTLineDraw(line, ctx)
}
}
}
class View: UIView {
override func drawRect(rect:CGRect)
{
// obtain context
let ctx = UIGraphicsGetCurrentContext()
// decide on radius
let rad = CGRectGetWidth(rect)/3.5
let endAngle = CGFloat(2*M_PI)
// add the circle to the context
CGContextAddArc(ctx, CGRectGetMidX(rect), CGRectGetMidY(rect), rad, 0, endAngle, 1)
// set fill color
CGContextSetFillColorWithColor(ctx,UIColor.grayColor().CGColor)
// set stroke color
CGContextSetStrokeColorWithColor(ctx,UIColor.whiteColor().CGColor)
// set line width
CGContextSetLineWidth(ctx, 4.0)
// use to fill and stroke path (see http://stackoverflow.com/questions/13526046/cant-stroke-path-after-filling-it )
// draw the path
CGContextDrawPath(ctx, CGPathDrawingMode.FillStroke);
secondMarkers(ctx!, x: CGRectGetMidX(rect), y: CGRectGetMidY(rect), radius: rad, sides: 60, color: UIColor.whiteColor())
drawText(rect, ctx: ctx!, x: CGRectGetMidX(rect), y: CGRectGetMidY(rect), radius: rad, sides: 12, color: UIColor.whiteColor())
}
}
obviously you would need let newView = View(frame: CGRect(x: 0, y: 0, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame))); self.view.addSubview(newView)
in the viewDidLoad
method of your viewController...
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Did you end up finishing this? I'm working on a similar project, but having difficulty connecting the NSTimer and the NSDate functions up to my draw methods. I'm still pretty new at swift.