Having owned an Apple Watch for over a year, I’ve grown a little bored with the available watch faces. So I was very impressed with the new, special edition Hermes watch face. This is quite different to any of the other faces on the Apple Watch. Unfortunately, it’s only available to new purchasers who buy the brand new Hermes Apple Watch – starting at £1000.
Having written my first Watch App recently, I thought I’d have a go at writing a Watch Face App. Apple WatchKit doesn’t officially support writing watch faces, so this would have to be an ordinary App that happens to display the time. There are plenty of custom faces you can use as wall-paper to create an attractive digital watch, but I wanted the analogue look.
The approach I took was:
- Start with an image e.g. from the custom faces library
- Look up how to get the current time in the WatchKit API
- Find out how to draw graphics on top of an image
- Work out the maths to draw the hands in the right place
- Work out how to render proper watch hands
I already blogged about how to get the current time and how to draw graphics on top of an image. So if you followed along, you can replace your Treasure Map image with a watch face image and use the createContext(), drawLine() and applyContextToImage() methods to draw the hour and minute hands. In fact, by making the colour a parameter of drawLine(), you’ve got the method to draw the second hand too.
As for the maths to draw the hands in the right place, it’s trigonometry. Both sin and cos are available in WatchKit, so convert the hours/minutes into radians and calculate the end coordinate of your watch hand, treating the length of the hand as the hypotenuse of a right-angled triangle.
[edit]I had a request to post the maths for this. Suppose you want to draw a hand at 12 minutes past. Convert that into a fraction of how far the hand has moved – here, 12 mins out of a possible 60 mins. Then work out whether it’s in the top-right, bottom-right, bottom-left or top-left quadrant of the watch face (that affects which sin/cos functions you’ll use) – here, 12/60 = 0.2, so it’s between 0.0 and 0.25, hence it’s in the first quadrant.
To calculate the x and y coordinates for the top-right quadrant, we form a right-angled triangle between the hand and the y-axis. The hand forms the hypotenuse of the triangle, let’s choose length = 100. So knowing the minutes as a fraction, and the length of the hand, here’s a function to calculate the x and y coordinates as an offset from the centre of the watch face, using trigonometry:
func handCoordinate( _ fraction : CGFloat, length : CGFloat ) -> (x : CGFloat, y : CGFloat ) { let x : CGFloat, y : CGFloat if ( 0 <= fraction && fraction < 0.25 ) { let theta = CGFloat( (fraction/0.25) * π / 2.0 ) x = length * sin( theta ) y = -1.0 * length * cos( theta ) } else { // remaining quadrants left as exercise for the reader! } return (x,y) }
The simplest way of drawing the hand is then a simple drawLine() from (centreX, centreY) to (centreX + x, centreY + y). For my 42mm watch, centreX and centreY are (156,150) – other sizes will vary.
[/edit]
That leaves the trick of how to draw a proper watch hand given only the drawLine() and drawCircle() methods. This is the method I used:
In order to draw the white circle outline with solid black inner-circle, I used this method:
func drawCircle( context : CGContext?, radius : CGFloat, centreX : CGFloat, centreY : CGFloat, colour : CGColor ) { let diameter = radius * 2.0 let rect = CGRect( x: centreX - radius, y : centreY - radius, width : diameter, height : diameter ) CGContextSetFillColorWithColor( context, UIColor.blackColor().CGColor ) CGContextFillEllipseInRect( context, rect ) CGContextSetLineWidth( context, 2.0 ) CGContextSetStrokeColorWithColor( context, colour ) CGContextStrokeEllipseInRect( context, rect ) }
There are a couple of limitations:
- You need to configure the Apple Watch to keep Apps running rather than return to the clock
- You cannot hide the status bar in a third party App, so it sits above the watch face and displays the digital time
That said, I’m really happy with the results:
This is actually one Watch App – I changed the Image to a Button so that I could iterate through different watch faces by tapping the watch. If you do this, call button.setBackgroundImage on the button instead of image.setImage.
See also: How to draw text onto an image and How to draw on top of an image.
Pingback: How to draw text onto an image in Apple Watch App | musingstudio
Pingback: How to learn Python | musingstudio
Pingback: How to use the Digital Crown in a Watch Face App | musingstudio
Pingback: How to switch watch faces using swipe gestures | musingstudio
Pingback: How to draw a triangle in Apple Watch | musingstudio
could you post how you did the math?
Sure, I’ve posted an update that shows how to work it out.
In the code for the watch hands i keep getting 2 errors at the “return (x, y)” line, the errors are “constant ‘y’ used before being initialized”
Yes, you need to fill in the code for the remaining quadrants (which I left as an exercise for the reader).
Fantastic. Do you think this would get approved in the app store? It’s “just” an app and doesn’t change the standard watch faces…
My understanding is that they don’t allow apps whose primary purpose is telling the time.