I’ve just released a new, small Swift library for loading and rendering a useful subset of SVG on macOS or iOS. I named it “Yass” - “yet another Swift SVG [library]”.

That “useful subset” consists of icons, drawn out of common shapes and paths, with simple colors; no scripting, gradients, text, animation, etc. See example icons below. I built it a few months ago, adding it to version 1.7 of my Mac app TimeStory, which has used it since, and I thought others might find it useful.

Yass is:

Yass’s design

As mentioned above, Yass uses Swift value types to model SVG data; its model is built around a few enums defining the available elements, shapes, and path instructions, and a few structs packaging up bundles of attributes. Here are a couple of examples:

public enum SVGElement {
    indirect case svg(String?, SVGFragmentAttributes, SVGPresentationAttributes, [SVGElement])
    indirect case group(String?, SVGPresentationAttributes, [SVGElement])
    case graphic(String?, SVGPresentationAttributes, SVGGraphic)
}

public enum SVGGraphic {
    case path([SVGPathInstruction])

    case rect(CGRect)
    case circle(c: CGPoint, r: CGFloat)
    case ellipse(c: CGPoint, rx: CGFloat, ry: CGFloat)
    case line(p1: CGPoint, p2: CGPoint)
    case polyline([CGPoint])
    case polygon([CGPoint])
}

This means that the drawing and path-construction code can use switch statements to cover all bases. For example, this is from my CGContext extension:

public func svg_buildPath(_ graphic: SVGGraphic) {
    beginPath()
    switch graphic {
    case .path(let instrs):
        svg_buildPath(instrs)
    case .rect(let rect):
        addRect(rect)
    case .circle(c: let c, r: let r):
        addEllipse(in: CGRect(x: c.x - r, y: c.y - r,
                              width: r * 2, height: r * 2))
    case .ellipse(c: let c, rx: let rx, ry: let ry):
        addEllipse(in: CGRect(x: c.x - rx, y: c.y - ry,
                              width: rx * 2, height: ry * 2))
    case .line(p1: let p1, p2: let p2):
        move(to: p1)
        addLine(to: p2)
    case .polyline(let points):
        addLines(between: points)
    case .polygon(let points):
        addLines(between: points)
        closePath()
    }
}

Contrast this approach with an object-oriented or protocol-oriented approach, more common in other SVG libraries that I looked at, where each SVG element or shape exposes a “draw” or “addToPath” method and encapsulates its implementation.

Here, drawings are described by data structures which are independent of destination, and their types have no knowledge of Core Graphics (other than the use of CGRect, CGPoint, and CGFloat). The actual path-drawing code lives in one file, which uses Swift switch statements to pattern-match, destructure, and recurse. Now imagine extending this library to support SwiftUI by creating Path objects, or even Cairo for non-Apple platforms. Each of those would again be a simple, decoupled set of pattern-matching code.

Example shapes

Here is a screenshot of TimeStory’s shape picker. The shapes following the octagon are all rendered by Yass from embedded SVG assets. (The first few are older shapes, which I implemented with direct Core Graphics path-drawing code; I didn’t feel the need to delete that code.)

When you add one of these to your document, it is given a size and color based on other properties.

Screenshot of shape picker from TimeStory 2.0

The code

I use GitLab for all my development, private and public. Find Yass here. (I will probably set up a GitHub mirror at some point.) I have released it under the MIT license, so you can use this in your nonfree, closed-source apps, if you find it useful.

I’ve recently started using the Swift Package Manager to organize my own internal libraries. It’s quite nice, and Xcode’s integration with SPM works reasonably well. So Yass is packaged for SPM, with two targets, a static library and a dynamic framework; I use them both (the static library for my TimeStory CLI target, and the framework for my Mac and iOS targets).

(I don’t, at present, use CocoaPods or Carthage for anything, so no support for them out of the box.)

I’m happy to hear any input on this library, or just a quick note if you’re using it, but feel free to take it, modify it, and use it as you wish.