Skip to content

Instantly share code, notes, and snippets.

@sketchytech
Last active March 30, 2021 13:47
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sketchytech/d0a8909459aea899e67c to your computer and use it in GitHub Desktop.
Save sketchytech/d0a8909459aea899e67c to your computer and use it in GitHub Desktop.
Swift: Draw a clock face (part 1)
// 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())
}
}
@afeathers
Copy link

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.

@MoralCode
Copy link

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