![[SwiftUI] ZStackとCircleを組み合わせて円形のプログレスバーを作る](https://devio2023-media.developers.io/wp-content/uploads/2019/07/190706_ios_catch.png)
[SwiftUI] ZStackとCircleを組み合わせて円形のプログレスバーを作る
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
こんにちは。CX事業本部の平屋です。
本記事では、以下のような「円形のプログレスバー」をSwiftUIで作る実装を紹介します。

検証環境
- macOS Monterey 12.6
- Xcode Version 13.4
ベースの作成
まずはビューを2つ作成します。
CircularProgressBar: プログレスバーのビューContentView: プログレスバーを使うビュー
// プログレスバーのビュー
struct CircularProgressBar: View {
@Binding var progress: CGFloat
var body: some View {
ZStack {
}
}
}
// プログレスバーを使うビュー
struct ContentView: View {
@State var progressValue: CGFloat = 0.3
var body: some View {
VStack {
CircularProgressBar(progress: $progressValue)
.frame(width: 150.0, height: 150.0)
.padding(32.0)
Spacer()
}
}
}
今回紹介するプログレスバーの実装では、以下の3つをCircularProgressBar内のZStackに重ねて実現します。
- 背景の円
- 進捗を示す円
- 進捗率のテキスト
背景の円の作成
まずは、CircularProgressBar.bodyのZStackに「背景の円」を追加します。
円を追加
ZStackにCircle()を追加すると、塗りつぶしの円が描画されます。
// プログレスバーのビュー
struct CircularProgressBar: View {
@Binding var progress: CGFloat
var body: some View {
ZStack {
// 背景の円
Circle()
}
}
}

スタイルの適用
円形の線を描画するように修正します。
ZStack {
// 背景の円
Circle()
// 円形の線を描画するように指定
.stroke(lineWidth: 24.0)
.opacity(0.3)
.foregroundColor(.blue)
}
「背景の円」はこれで完成です。

進捗を示す円の作成
次に、「進捗を示す円」をZStackの手前側に追加します。
円を追加
ZStackにCircle()を追加します。背景の円と異なり、strokeモディファイアにStrokeStyleを与えて線の端の形状などを指定しています。
ZStack {
// 背景の円
// ...
// 進捗を示す円
Circle()
// 線の端の形状などを指定
.stroke(style: StrokeStyle(lineWidth: 24, lineCap: .round, lineJoin: .round))
.foregroundColor(.blue)
}
この時点では2つの円が同じ形状になっていて「背景の円」が全て隠れている状態です。

進捗分だけ描画されるようにする
進捗分だけ描画されるように修正します。
ZStack {
// 背景の円
// ...
// 進捗を示す円
Circle()
// 始点/終点を指定して円を描画する
// 始点/終点には0.0-1.0の範囲に正規化した値を指定する
.trim(from: 0.0, to: min(progress, 1.0))
// 線の端の形状などを指定
// ...
}
現時点の描画結果は以下のようになります。(円のデフォルトの原点は「時計の3時の位置」のようです。)

円の原点を修正する
rotationEffectモディファイアを使って円の原点を修正します。
ZStack {
// 背景の円
// ...
// 進捗を示す円
Circle()
// ...
.foregroundColor(.blue)
// デフォルトの原点は時計の12時の位置ではないので回転させる
.rotationEffect(Angle(degrees: 270.0))
}
期待する表示になりました。「進捗を示す円」はこれで完成です。

進捗率のテキストの作成
最後に、「進捗率のテキスト」をZStackの手前側に追加します。
ZStack {
// 背景の円
Circle()
// ...
// 進捗を示す円
Circle()
// ...
// 進捗率のテキスト
Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0))
.font(.largeTitle)
.bold()
}
期待する表示になりました。これで完成です。

まとめ
完成版のコード全体は以下の通りです。
// プログレスバーのビュー
struct CircularProgressBar: View {
@Binding var progress: CGFloat
var body: some View {
ZStack {
// 背景の円
Circle()
// ボーダーラインを描画するように指定
.stroke(lineWidth: 24.0)
.opacity(0.3)
.foregroundColor(.blue)
// 進捗を示す円
Circle()
// 始点/終点を指定して円を描画する
// 始点/終点には0.0-1.0の範囲に正規化した値を指定する
.trim(from: 0.0, to: min(progress, 1.0))
// 線の端の形状などを指定
.stroke(style: StrokeStyle(lineWidth: 24, lineCap: .round, lineJoin: .round))
.foregroundColor(.blue)
// デフォルトの原点は時計の12時の位置ではないので回転させる
.rotationEffect(Angle(degrees: 270.0))
// 進捗率のテキスト
Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0))
.font(.largeTitle)
.bold()
}
}
}
// プログレスバーを使うビュー
struct ContentView: View {
@State var progressValue: CGFloat = 0.3
var body: some View {
VStack {
CircularProgressBar(progress: $progressValue)
.frame(width: 150.0, height: 150.0)
.padding(32.0)
Spacer()
}
}
}










