iosswiftswiftuidynamicdynamic-island

How do I only draw the top part of a circumference?


Im trying to draw an arc in SwiftUI, im practicing and I want to make this view (see in the picture) from the apple website where it shows how to implement Dynamic Island live activities.

This is the view im trying to replicate

I have tried using path but im not sure how to only draw an arc and not a half circle like my code does.

Here is the code using Path:

  struct ArcShape: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius: CGFloat = 100
        let startAngle = Angle(degrees: 180)
        let endAngle = Angle(degrees: 0) 
        path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
        
        return path
    }
}

And here is a closer approach usingCircle and trimming it, but I don't know how to "mush it" and make it flatter, and also round the corners, I've tried using .cornerRadius and making the frame wide and not tall but I didn't see any result, only the circle adjusting to the smaller size on the frame:

Circle()
   .trim(from: 0.55, to: 0.95)
   .stroke(.linearGradient(colors: [.blue, .cyan],
                          startPoint: .leading,
                          endPoint: .trailing), lineWidth: 5)

Solution

  • Paths use angles that start at zero on the right (east in compass directions) and increase as you go clockwise.

    You created an arc that started at 180 degrees (due west) and went to 0 degrees (due east) drawing counter-clockwise. That drew a half circle.

    If you want to draw less of the circle, add some offset the starting angle and subtract the same amount from the ending angle. So, as Paulw11 suggested, try 180+40 = 220 degrees for the left (west) side of your arc, and 0-40 = -40, or 360-40 = 320 degrees for the ending, right side of your arc.

    This code:

    struct ArcShape: Shape {
        var radius: CGFloat // The circle radius to use. Bigger numbers make a flatter but bigger arc
        var arcOffset: CGFloat // The number of degrees above the center to start the left and right of the arc.
        
      func path(in rect: CGRect) -> Path {
          var path = Path()
          let center = CGPoint(x: rect.midX, y: rect.midY)
          let startAngle = Angle(degrees: 180 + arcOffset)
          let endAngle = Angle(degrees: 0 - arcOffset)
          path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
          
          return path
      }
    }
    
    struct ContentView: View {
        var body: some View {
            // GeometryReader lets us learn things like the size of the screen
            GeometryReader { proxy in
                VStack(alignment: .center) {
                    Spacer()
                        .frame(height: 100)
                    let inset: CGFloat = 10
                    let frameSize = proxy.size.width - inset * 2
                    // Draw 2 arcs on top of each other.
                    ZStack {
                        // The first blue arc will be from
                        // 180-30 = 210° to
                        // 360-30 = 330°
                        ArcShape(radius: frameSize / 2 - 20, arcOffset: 30)
                            .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
                            .foregroundColor(Color(red: 0.2, green: 0.2, blue: 0.7))
                            .frame(width: frameSize, height: frameSize)
                        // The second cyan arc will be from
                        // 180-70 = 110° to
                        // 360-70 = 190°
                        ArcShape(radius: frameSize / 2 - 20, arcOffset: 70)
                            .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
                            .foregroundColor(.cyan)
                            .frame(width: frameSize, height: frameSize)
                    }
                    .border(Color.green, width: 1) //Remove if you don't want a border
                    .padding(.leading, inset)
    
                }
            }
        }
    }
    
    #Preview {
        ContentView()
    }
    

    Yields an image that looks like this:

    enter image description here