SwiftUIでの半円形プログレスリング

![demo-auto_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/58ec602d-e910-3e47-3df0-932cc6234003.gif)

新しいAIアプリを開発しています。そして、進行状況（またはスコア）を表示するためにSwiftUIで半円のプログレスリングを作成しました。

これは円形の要素を使用しますが、円の上半分のみが表示されるようにクロップします。

SwiftUIで.trimビューモディファイアを使用して、円の上半分だけを表示しました。しかし、円のフレームは変わらず、そのため円の下に白いスペースが存在するかもしれません。

これは、円をより小さいコンテナ上にオーバーレイすることで解決できますが、この記事ではそのままのコードを共有します。

```swift
import Foundation
import SwiftUI

public struct HalfCircularProgressRing: View {
    
    private var progressPercentageToShow: Double
    @State private var currentProgressValue: Float = 0
    private let timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
    
    static let circleTrimStartPosition: CGFloat = 0.3
    static let circleTrimEndPositon: CGFloat = 0.9
    static let colorfulGradientPattern: AngularGradient = .init(gradient: Gradient(stops: [
        .init(color: .red, location: 0.4),
        .init(color: .orange, location: 0.5),
        .init(color: .yellow, location: 0.6),
        .init(color: .green, location: 0.7),
        .init(color: .blue, location: 0.8)
    ]), center: .center)
    
    public init(progressPercentageToShow: Double) {
        self.progressPercentageToShow = progressPercentageToShow
    }
    
    public var body: some View {
        ZStack {
            // background circle
            Circle()
                .trim(from: Self.circleTrimStartPosition, to: Self.circleTrimEndPositon)
                .stroke(style: .init(lineWidth: 12, lineCap: .round, lineJoin: .round))
                .opacity(0.3)
                .foregroundColor(Color.gray)
                .rotationEffect(.degrees(55))
            
            // progress circle
            Circle()
                .trim(from: Self.circleTrimStartPosition, to: Self.circleTrimStartPosition + CGFloat(currentProgressValue) * (Self.circleTrimEndPositon - Self.circleTrimStartPosition))
                .stroke(style: .init(lineWidth: 12, lineCap: .round, lineJoin: .round))
                .fill(Self.colorfulGradientPattern)
                .rotationEffect(.degrees(55))
            
            // progress text
            Text("\(Int(progressPercentageToShow * 100))%")
                .font(.system(size: 60))
                .bold()
        }
        .frame(width: 250, height: 250)
        .onReceive(timer) { _ in
            withAnimation {
                let convertedProgressPercentage = Float(progressPercentageToShow)
                if currentProgressValue < convertedProgressPercentage {
                    currentProgressValue = min(currentProgressValue + 0.006, convertedProgressPercentage)
                } else if currentProgressValue > convertedProgressPercentage {
                    currentProgressValue = max(currentProgressValue - 0.006, convertedProgressPercentage)
                }
            }
        }
    }
    
}

struct HalfCircularProgressRing_Previews: PreviewProvider {
    struct PreviewContainer: View {
        @State private var currentProgress: Double = 0.8
        var body: some View {
            VStack {
                HalfCircularProgressRing(progressPercentageToShow: currentProgress)
                Stepper("Value is \(Int(currentProgress * 100))%", value: $currentProgress, in: 0...1, step: 0.01)
                Spacer()
            }
            .padding()
        }
    }
    static var previews: some View {
        PreviewContainer()
    }
}
```