はじめに
こんにちは。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()
}
}
}