Coding, SwiftUI

Paths

Paths –

So, earlier this week I was learning how to use the Charts framework and started customizing the symbols. There are a bunch of basic ones (see the BasicChartSymbolShape struct). You can also pass in a custom view to use as a symbol. This is cool, but I thought it might be cool to build my own custom shape.

To create a custom symbol shape (like BasicChartSymbolShape) you must conform to the ChartSymbolShape protocol (which conforms to the Shape protocol). So how do we do this? The easiest way to find out is create a struct and declare it’s conformance and see what the compiler says:

stuck CustomChartSymbolShape: ChartSymbolShape {

}

Of course, the compiler informs us that we neither conform to ChartSymbolShape nor Shape, but it offers to help us. Lets select fix:

struct AppChartSymbolShape: ChartSymbolShape {
    var perceptualUnitRect: CGRect
    
    func path(in rect: CGRect) -> Path {
        
    }
}

Perfect. Now we’re cooking. First things first, let’s provide a path. Path is a SwiftUI struct used to create all sorts of shapes (like Circle or Rectangle). I’ve got an app called StemFox whose logo is a fox face sorta shaped like a book. After some trial and error I came up with this:

// 1. Declare conformance to shape
struct StemFox: Shape {
    // 2. Create the path
    func path(in rect: CGRect) -> Path {
        
        // 3. helper variables
        let maxX = rect.maxX
        let midX = rect.midX
        let maxY = rect.maxY
        let minX = rect.minX
        let minY = rect.minY
        
        // 4. Adjust for frame location differences
        let yDifference = maxY - minY
        let yLower = 2 * yDifference / 3 + minY
        let yUpper = yDifference / 3 + minY

        // 5. Construct the path
        var path = Path()

        path.move(to: CGPoint(x: minX, y: minY))
        path.addLine(to: CGPoint(x: midX, y: yUpper))
        path.addLine(to: CGPoint(x: maxX, y: minY))
        path.addLine(to: CGPoint(x: maxX, y: yLower))  
        path.addLine(to: CGPoint(x: midX, y: maxY))
        path.addLine(to: CGPoint(x: minX, y: yLower))
        path.addLine(to: CGPoint(x: minX, y: minY))

        return path
    }
}

What’s going on here:

  1. We create a shape called StemFox and conform to the Shape protocol.
  2. To conform to Shape, we must provide the path(in rect: CGRect) method
  3. We create some helper variables, just for reference.
  4. We must scale our positions appropriately. If we don’t do this step, and instead only used something like maxY / minY here, the shape would only work as a stand alone view whose origin is always at (0,0). The rect passed in when used for data points in charts are in the space of the chart as a whole, with the origin bouncing all over the place. Without this step, the rendering gets very odd indeed.
  5. With all the math done, we just connect the dots to draw our shape.

We can use StemFox in SwiftUI like any other shape:

StemFox()
    .frame(width: 50, height: 50)

And it produces a fair rendition of the original logo:

Results from StemFox()

As cool as this looks, most Chart symbols are outlines, so let’s make this an outline. We can do this by returning the stroked path version of our path: return path.strokedPath(StrokeStyle()). This results in a very nice outline:

Results from StemFox() when returning path.strokedPath(.StrokedStyle())

Awesome! Now lets use this shape in our AppChartSymbolShape:

struct AppChartSymbolShape: ChartSymbolShape {
 
    var perceptualUnitRect: CGRect

    func path(in rect: CGRect) -> Path {
        StemFox()
            .path(in: rect)
    }
}

Now we just need to create an extension on ChartSymbolShape to enable easy use:

extension ChartSymbolShape where Self == AppChartSymbolShape {
    /// StemFox symbol.
    static var stemFox: AppChartSymbolShape {
        AppChartSymbolShape(perceptualUnitRect: .unit)
    }
}

// And for ease of use
extension CGRect {
    static var unit: CGRect {
        CGRect(origin: .zero, size: .unit)
    }
}

extension CGSize {
    static var unit: CGSize {
        CGSize(width: 1, height: 1)
    }
}

Finally we are ready to use it in a chart:

import Charts

struct Info: Identifiable {
    let id = UUID()
    let value: Double
    let date: Int
}

let data = [
    Info(value: 10, date: 0),
    Info(value: 1, date: 1),
    Info(value: 4, date: 2),
    Info(value: -3, date: 3),
    Info(value: 4, date: 4),
]

struct ContentView: View {
    var body: some View {
        VStack {
            
            Chart(data) { data in
                LineMark(
                    x: .value("Date", data.date),
                    y: .value("Frequency", data.value)
                )
                .symbol(.stemFox)
            }
            .frame(height: 250)
            .chartYAxisLabel(position: .automatic, alignment: .trailing, content: {
                Text("Frequency")
            })
            
            StemFox()
                .frame(width: 50, height: 50)
            
            Text("Hello, world!")
        }
        .padding()
    }
}

This code yields the following chart and shape:

Final product!

Not difficult and kinda fun!

Further Reading:

https://www.hackingwithswift.com/books/ios-swiftui/creating-custom-paths-with-swiftui

https://developer.apple.com/documentation/swiftui/shape

https://developer.apple.com/documentation/charts/chartsymbolshape

Book Reviews, Business

Building a Story Brand

By Donald Miller

I really enjoyed this book. The premise of the book is that in order to add clarity to your marketing and brand, you should follow the rules that a good storytelling follow:

  1. A Character
  2. Has a problem
  3. Meets a guide
  4. Who gives them a plan
  5. And calls them to action
  6. Which helps them avoid failure
  7. And this ends in success.

Most of the book is spend building up each of these themes in a business context. For example, when examining the character (your customer) and their problems, you should understand that all people have fundamental needs and your product should focus on providing for one of these needs.

I enjoy good storytelling and incorporating storytelling into marketing (something I know very little about) seemed like an improvement. Overall, I found the ideas intriguing enough to try them out in my own endeavors. I used the framework to redesign my subscription page in StemFox and will use them as my framework for my messaging and communications until I find something better.

The end of the book includes practical advice on applying the principals of the book in your business.

Quotes I Underlined

“What Frustrations Do Our Products Resolve?”

Page 64

Always worth keeping in mind. If a feature or product isn’t resolving a frustration, why are we adding it?

The best way to arrive at an agreement plan is to list all the things your customer might be concerned about as it relates to your product or service and then counter that list with agreements that will alleviate their fears.

Page 92

I like this quote because it reminded me of a tactic from Never Split the Difference by Chris Voss. At some point he talks about heading into negotiations by listing all the problems your counterpart might have with you. By simply acknowledging these perceived shortcomings you can ease their pain.

Conclusion

It’s a good book. Worth the read if you are looking for direction or clarity on how to market to and communicate with your customers. There is an accompanying website that helps map out the different aspects of building a Story Brand.