Category: Swift Programming

Drawing a Diamond with SwiftUI and CoreGraphics

Screenshot of Diamonds rendered without applying closeSubpath()
Screenshot of Diamonds rendered without applying closeSubpath()

It took me a little time to figure ot how to draw a simple diamond using Xcode 16.0 and Swift 5. The trouble I ran into was the lines connecting the start and end point were not closing. I could fudge it a little by increasing the length of the start and end lines – BUT that wouldn’t really work because with width of the stroke changes the required fudge-factor.

The solution to the problem is calling closeSubpath() on your path variable to format the start/end match points properly.

Now, you wouldn’t see this problem if you were coding with fill or stroking in a different fashion than using .stroke or .strokeBorder as I did. If you were just filling then you don’t see the stroke lines. Also stroke works perfect with a 1 pixel line without calling closeSubpath(). It’s when the. line gets to be about 4 pixels wide that it becomes visible. To use those two methods, Diamond needed to conform to InsettableShape, and thus implement:
inset(by amount: CGFloat) -> some InsettableShape

Screenshot of Xcode 16.0 Preview of 2 diamonds showing on iPhone 16 Pro
All Fixed! Rendering of the shown code using Xcode 16.0 and Swift 5 (Click to enlarge)

You control the shape of the rendered diamond by placing it into a container and it will fit the size.

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Diamond()
                .inset(by: 5)
                .fill(.red)
                .strokeBorder(.blue, lineWidth: 10)
                .frame(width:200, height:300)
                .background(.green)
            Diamond()
                .inset(by: 15)
                .fill(.orange)
                .strokeBorder(.black, lineWidth: 16)
                .frame(width:150, height:250)
                .background(.green)
    }
    .padding()
    .background(.yellow)
    }
}

#Preview {
    ContentView()
}
//
//
//  Diamond.swift
//

import SwiftUI
import CoreGraphics
// -------------------------------------------------------------------------
// We make diamond InsetableShape and add func required by protocol: inset()
// then we can use stroke on shape and get an outline
// -------------------------------------------------------------------------
struct Diamond: InsettableShape {
    var insetAmount = 0.0
   
    func inset(by amount: CGFloat) -> some InsettableShape {
        var diamond = self
        diamond.insetAmount += amount
        return diamond
    }
    
    
    func path(in rect: CGRect) -> Path {
        
        // draws counter clockwise from 3 o'clock
        let start0 = CGPoint(x: rect.maxX - insetAmount, y: rect.midY)
        
        // 3 o'clock to 12 o'clock
        let end0 = CGPoint(x: rect.midX, y: rect.maxY - insetAmount)
        // 12 o'clock to 9 o'clock
        let end1 = CGPoint(x: rect.minX + insetAmount, y: rect.midY)
        // 9 o'clock to 6 o'clock
        let end2 = CGPoint(x: rect.midX, y: rect.minY + insetAmount)
        // 6 o'clock to 3 o'clock
        let end3 = CGPoint(x: rect.maxX - insetAmount, y: rect.midY)

        var p = Path()
        
        p.move(to: start0)
        p.addLines([start0, end0, end1, end2, end3])

        // complete drawing the diamond by applying
        // closeSubpath - otherwise joints won't fill
        // in between start point and end point
        p.closeSubpath()
        
        return p
    }
}