ios – Trim & rounded lineCap for a circle in SwiftUI

ios – Trim & rounded lineCap for a circle in SwiftUI


I’d write this as a Form that’s crammed, as an alternative of a Path that’s stroked.

The trail of the Form is the union of:

  • a path created by the strokedPath of a (trimmed) round path, with the .butt line cap.
  • a round path with the radius of the road width, at the place the trimmed path ends.
struct OneEndRoundedCircle: Form {
    var trim: CGFloat
    let lineWidth: CGFloat
    
    func path(in rect: CGRect) -> Path {
        let radius = min(rect.width, rect.top) / 2
        let middle = CGPoint(x: rect.midX, y: rect.midY)
        let circlePath = Path(
            ellipseIn: CGRect(origin: middle, dimension: .zero)
                .insetBy(dx: -radius, dy: -radius)
        ).trimmedPath(from: 0, to: trim)
        let stroked = circlePath.strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: .butt))

        // that is the place we wish the rounded finish to be. 
        // In case your path begins some other place, modify the angle accordingly
        let roundedPoint = middle.making use of(.init(
            translationX: radius * sin(.pi / 2),
            y: radius * cos(.pi / 2)
        ))
        let littleCircle = Path(
            ellipseIn: .init(origin: roundedPoint, dimension: .zero)
                .insetBy(dx: -lineWidth / 2, dy: -lineWidth / 2)
        )
        return stroked.union(littleCircle)
    }
}

// Animatable conformance in case you wish to animate the quantity trimmed
extension OneEndRoundedCircle: Animatable {
    var animatableData: CGFloat {
        get { trim }
        set { trim = newValue }
    }
}
non-public func speedProgressView(width: CGFloat) -> some View {
    ZStack {
        // deliberately elevated lineWidth to make the rounded finish extra seen
        OneEndRoundedCircle(trim: 0.2, lineWidth: 10)
            .fill(.crimson)
            .shadow(coloration: .inexperienced, radius: 10, x: 0, y: 0)
            .shadow(coloration: .inexperienced, radius: 2, x: 0, y: 0)
    }
    .body(width: width)
    .rotationEffect(.levels(100))
}

Instance output:

Example output

For iOS 17 or earlier, union isn’t out there, however you possibly can simply work with CGPaths as an alternative, and use its union methodology as an alternative.


To realize the outcome within the screenshot, the place each ends are rounded when the speedometer is full, you possibly can simply examine the worth of trim and return early.

// assuming you wish to depart the underside 20 levels of the circle all the time empty,
// the speedometer is full when the trim ends at 17/18
if abs(trim - 17 / 18) < 0.00001 {
    return circlePath.strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: .spherical))
}
let stroked = circlePath.strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: .butt))
// add the little circle like earlier than...

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
rooshohttps://www.roosho.com
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here


Latest Articles

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog.