LoginSignup
28
8

More than 1 year has passed since last update.

SwiftUIで円形のプログレスバーを作る

Last updated at Posted at 2022-12-07

こんにちは!
アイスタイル Advent Calender 2022 8日目の記事をを担当します。hayakawatです。
アイスタイルではアプリ開発グループでiOSアプリの開発をしています。
今回は最近個人で開発したアプリで利用した円形のプログレスバーについて書こうと思います。

今回作るもの

円形のプログレスバーとはiPhoneのバッテリーウィジェット等で使われている緑の円のやつです。iPhoneユーザの方は見覚えあるかと思います。

作り方

緑の円と背景のグレーの円をピッタリ重ねることで円が満たされていくように見せています。
ZStackで同じ大きさのCircle()をピッタリ重ねて表示します。

struct CircleProgressView: View {
    var body: some View {
        ZStack {
            // 下の円
            Circle()
                .stroke(
                    Color.gray,
                    style: StrokeStyle(
                        lineWidth: 15, // 線の太さ
                        lineCap: .round) // 線の端の形状
                )
                .opacity(0.5) // 透明度
                .frame(width: 150, height: 150) // 大きさ
            // 上の円
            Circle()
                .trim(from: 0.0, to: 0.5) // 線のトリム
                .stroke(
                    Color.green,
                    style: StrokeStyle(
                        lineWidth: 15,
                        lineCap: .round)
                )
                .frame(width: 150, height: 150)
        }
    }
}

.strokeを付けることで塗りつぶしの円から線の円にしています。
上の円に追加した.trim(from: 0.0, to: 0.5)は円をどの位置でトリムするかを決めています。
今回の場合だとちょうど半分になります。
toの値を変更することで円の進み具合を変更することができます。
するとこんな感じになります。
スクリーンショット 2022-12-05 1.16.41.png

90度くらいの位置から始まりちょうど半分まで円が描画されています。
しかし、バッテリーウィジェットは上の位置から始まっているので、今回作っているプログレスバーも上から始まるようにします。
そのために上の円に.rotationEffect(Angle(degrees: -90))を追加します。

struct CircleProgressView: View {
    var body: some View {
        ZStack {
            Circle()
                .stroke(
                    Color.gray,
                    style: StrokeStyle(
                        lineWidth: 15,
                        lineCap: .round)
                )
                .opacity(0.5)
                .frame(width: 150, height: 150)
            Circle()
                .trim(from: 0.0, to: 0.5)
                .stroke(
                    Color.green,
                    style: StrokeStyle(
                        lineWidth: 15,
                        lineCap: .round)
                )
                .frame(width: 150, height: 150)
                .rotationEffect(Angle(degrees: -90)) // 追加
        }
    }
}

すると円の上からちょうど半分の真下まで満たされたViewを作成することができました。
スクリーンショット 2022-12-05 1.21.48.png

アニメーションさせる

さらに60秒で満たされるプログレスバーの進捗が満たされるようにしていきます。

ViewModelの作成

まずは進捗の割合などのロジックを記述するViewModelを作成します。

import Foundation
import SwiftUI
import Combine

final class CircleProgressViewModel: ObservableObject {
    @Published var progressValue: CGFloat = 0.0
    private var timerCount: CGFloat = 0.0
    private var cancellable: AnyCancellable?

    init() {
        startTimer()
    }

    private func startTimer() {
        cancellable = Timer.publish(every: 0.1, on: .main, in: .default)
            .autoconnect()
            .sink { [weak self] _ in
                self?.countProgress()
            }
    }

    private func countProgress() {
        if timerCount > 60 { cancellable?.cancel() }
        timerCount = timerCount + 0.1
        progressValue = timerCount / 60.0
    }
}

progressValueはView側で監視したいので@Publishedで宣言します。
startTimerではCombineを使用してcountProgressメソッドを0.1秒ごとに実行します。
countProgress内では先頭でtimerCountで60秒経過したかを判定しtrueならタイマーを破棄するようにしています。
falseならtimerCountに0.1を加算してCircleProgressView側の.trimの範囲である0.0〜1.0の間に収めるために60.0で割っています。

CircleProgressView側に反映する

では先ほど作成したCircleProgressViewModel内のprogressValueCircleProgressViewで監視してアニメーションするようにします。

struct CircleProgressView: View {
    // ViewModelをStateObjectで宣言してCircleProgressViewModelを監視できるようにする
    @StateObject var viewModel = CircleProgressViewModel()
    var body: some View {
        ZStack {
            Circle()
                .stroke(
                    Color.gray,
                    style: StrokeStyle(
                        lineWidth: 15,
                        lineCap: .round)
                )
                .opacity(0.5)
                .frame(width: 150, height: 150)
            Circle()
                .trim(from: 0.0, to: viewModel.progressValue) // toの値をViewModelのprogressValueを参照する
                .stroke(
                    Color.green,
                    style: StrokeStyle(
                        lineWidth: 15,
                        lineCap: .round)
                )
                .frame(width: 150, height: 150)
                .rotationEffect(Angle(degrees: -90))
        }
    }
}

このように.trimのtoの値をviewModel.progressValueから参照することで、↓のgifのように緑の円が進んでいく円形のプログレスバーの完成です。
progress_gif.gif

まとめ

意外と簡単に円形のプログレスバーを作ることができました。
もっと応用すれば面白い形のプログレスバーやグラフを描画することができそうですね。
それでは。よいお年を。

28
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
8