Tuesday, July 02, 2019

Textual Representation of SwiftUI Paths

While on vacation, I've been creating my first Swift Package, for my incomplete Scalar2D project. While my major ambitions have been slow, it does have 2 useful features: it supports many ways to represent colors as strings, and it can generate 2D paths from standard SVG path element strings.

So you can write:
import SwiftUI
import Scalar2D_SwiftUI
import CoreGraphics

public struct TestFrogView : View {
    private let cgPath = CGPath.path(fromSVGPath: "M185 212C139 183 40 199 39 108A18 18 0 1 1 56 75Q67 53 91 45A18 18 0 1 1 127 38C170 29 193 62 220 161L225 110Q231 84 260 115C260 142 265 205 241 198Q215 193 227 230C236 249 161 249 125 248A5 5 325 1 0 122 259C192 264 248 249 237 226Q230 206 247 211  266 210 272 161C273 139 276 106 252 93 245 86 209 65 216 133 200 46 176 19 132 26A28 28 0 0 0 81 40Q61 46 52 63A27 28 0 0 0 27 110C33 192 70 192 145 205Z"
)!

    public var body: some View
    {
       GeometryReader
        {
           proxy in
            Path(self.cgPath.fitting(geometry: proxy)).strokedPath(StrokeStyle(lineWidth:3.0))
        }.frame(idealWidth:cgPath.boundingBoxOfPath.width, idealHeight:cgPath.boundingBoxOfPath.height)
    }
}

Scalar2D_SwiftUI package in action

One thing, I found out after putting the package together was that the SwiftUI path has a String representable form of its own. You can pass it a specially formatted string and it will generate a Path struct.

Thus, an SVG Path to make a heart in a circle:
"M 0 0 A 100 100 0 0 0 0 200 A 100 100 0 0 0 0 0 Z M0 45 C 63-23 171 87 0 200 C -171 87 -63 -23 0 45 Z"
Simple SVG of a Heart in a Circle
Can be represented by a SwiftUI Path representation of:
0 0 m  -55.2285 0 -100 44.7715 -100 100 c -100 155.228 -55.2285 200 -1.83697e-14 200 c  55.2285 200 100 155.228 100 100 c 100 44.7715 55.2285 0 6.12323e-15 0 c h 0 45 m 63 -23 171 87 0 200 c -171 87 -63 -23 0 45 c h 
Which is probably just a direct translation of the underlying CGPath, which does not have an explicit arc element, so the arcs that made up my circle—the "A" operands—got converted to the "c"  cubic Bezier operands. The stringRepresentation property on Path tends to add unneeded short line segments after moves, which is also probably an artifact of iterating through the CGPath to generate the text. 

You could use the included Path(string: String) constructor to generate text from shapes, but SVG paths have the advantage of being generated by common drawing applications, and having more features such as arcs, shortcut operands and relative coordinates. I'm not sure why Apple didn't choose SVG paths when deciding on a text format, but please give my Scalar2D Swift Package a try.