Circle with an evenly dotted border in SwiftUI?

Viewed 2038

I'm trying to create a resizable button that has an evenly dotted circle border around it. If you simply put:

Circle()
  .stroke(color, style: StrokeStyle(lineWidth: 3, lineCap: .butt, dash: [3, radius / 3.82]))
  .frame(width: radius * 2, height: radius * 2)

There might be an uneven distribution of dots as you can see in the below image: enter image description here

Here's a related question with a solution which I tried adapting from UIKit into a SwiftUI struct but also failed.

Can someone please help me either find a way to adapt that "dash" value to create an evenly dashed stroked border with dependence on the radius OR create a custom shape instead?

2 Answers

I have an answer in pure SwiftUI. I think the issue you are running into is simply that you are drawing for part and skipping for part, and you have to take both into account.

Therefore, you need to come up with the circumference, divide it into the segments of 1 drawn part + 1 undrawn part. The drawn part is simply what looks good to you, so the undrawn part is the segment less the drawn part. You then plug both of those values into the StrokeStyle.dash and you get evenly spaced dots.

import SwiftUI

struct ContentView: View {
    let radius: CGFloat = 100
    let pi = Double.pi
    let dotCount = 10
    let dotLength: CGFloat = 3
    let spaceLength: CGFloat

    init() {
        let circumerence: CGFloat = CGFloat(2.0 * pi) * radius
        spaceLength = circumerence / CGFloat(dotCount) - dotLength
    }
    
    var body: some View {
        Circle()
            .stroke(Color.blue, style: StrokeStyle(lineWidth: 2, lineCap: .butt, lineJoin: .miter, miterLimit: 0, dash: [dotLength, spaceLength], dashPhase: 0))
            .frame(width: radius * 2, height: radius * 2)
    }
}

using some of the code in circle with dash lines uiview

I got this test code working for Swiftui:

    import SwiftUI
    
    @main
    struct TestApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    struct ContentView: View {

    @State var color = Color.blue
    @State var radius = CGFloat(128)
    @State var painted = CGFloat(6)
    @State var unpainted = CGFloat(6)
    
    let count: CGFloat = 30
    let relativeDashLength: CGFloat = 0.25

    var body: some View {
        Circle()
            .stroke(color, style: StrokeStyle(lineWidth: 3, lineCap: .butt, dash: [painted, unpainted]))
          .frame(width: radius * 2, height: radius * 2)
          .onAppear {
              let dashLength = CGFloat(2 * .pi * radius) / count
              painted = dashLength * relativeDashLength
              unpainted = dashLength * (1 - relativeDashLength)
          }
    }
}
Related