Pie Progress View (iOS and macOS)

Following on from my previous post about cross platform development, I wanted to showcase a simple view/layer implementation I recently built for an app I’m working on.

The design requires a pie chart to represent progress. I initially built it using CAShapeLayer however this quickly proved to be the wrong approach since the shape layer would attempt to ‘morph’ between values, rather than simply adjust the chart.

At this point I decided to move to a more custom approach. I also knew this would lend itself nicely to cross platform development, since layer’s are generally identical across both iOS and macOS.

I plan on doing some more in-depth posts that go into the deeper details of making a cross-platform application, but for the purposes of this post I’ll keep things short.

Cross Platform

I generally write the code for one platform first, one file at a time. Then assess the requirements to work out any extensions, typealias‘s, etc.. I may need.

On iOS its relatively simple to get a cgPath from a UIBezierPath however on macOS, we need to do this ourselves. So I wrote an equivalent extension.

#if os(OSX)
import AppKit

public extension NSBezierPath {
    var cgPath: CGPath { /* implementation */ }

Next up, I needed some typealias‘s to make it easier to use a single call throughout my code.

#if os(OSX)
    import AppKit
    public typealias Color = NSColor
    public typealias View = NSView

    private func scale() -> CGFloat {
        return NSScreen.main?.backingScaleFactor ?? 1

    extension NSView {
        internal var nonOptionalLayer: CALayer {
            return layer!
    import UIKit
    public typealias Color = UIColor
    public typealias View = UIView

    internal func scale() -> CGFloat {
        return UIScreen.main.scale

    extension UIView {
        internal var nonOptionalLayer: CALayer {
            return layer

Coordinate System

One of the most interesting differences between iOS and macOS development, is that the y-coordinates are inverted. Where iOS expects the origin to be top-left, macOS expects the origin to be bottom-left.

To normalise the origin for both platforms, NSView provides a simple isFlipped property. Returning true for this property, moves the origin to the top-left, matching what we’d expect on iOS.

public override var isFlipped: Bool {
    return true

Progress View

The code itself is extremely simple. We simply subclass CALayer to provide our own drawing for each frame. This allows us to animate the progress when its value changes. We even get all the benefits of implicit animation, etc…

I then wrapped the layer with a custom (UI/NS)View, added some IBDesignable support and some configurable properties to make it easier to work with.

Lastly I added a setProgress(progress:animated:) function which was as simple as disabling the implicit animation when animated == false.

public func setProgress(_ progress: CGFloat, animated: Bool) {
    guard animated else {
        self.progress = progress

    self.progress = progress


Building a cross platform component isn’t too difficult. Focusing on UI components at this level, allows the greatest amount of reuse as well. Adding storyboard/XIB support also aides the user of the component.

Scheduling Services

A micro-post about a micro-framework. Schedule (possibly repeating) events for some time in the future, along with restoration support. An NSNotification-based scheduling service in Swift.


  • Schedule a request
  • Cancel a request
  • Restore all requests upon app-launch
  • Pause/resume all requests

Essentially I wanted to remove the knowledge/management of a timer. Obviously the implementation would need to handle this, but I wanted to build an abstraction on top of this that would support all of my requirements, while providing a simple/clean API.

Scheduling Service

The result is a lightweight micro-framework – SchedulingService.

// schedule a request
let request = service.schedule(date: Date().addingTimeInterval(5))

// cancel the request

// schedule an NSNotification
service.schedule(date: Date().addingTimeInterval(20), 
         notification: .CountdownDidUpdate)


I also wanted to be able to save/restore all scheduled events so that I wouldn’t need to restore them manually upon launch.

// decodes or makes a new Service
let store = SchedulingServiceStore(service: nil)

// encodes the current service

Super simple, single file micro-framwork.