HuggingFaceのDemoを起動すると、3色のラインがProgressの周りをぐるぐる回るアニメーションが表示される。これがとても可愛らしいのでSwiftUIで再現してみました。
実際のリンク
https://huggingface.co/spaces/wyysf/GenMM
開発環境
Xcode : 14.3
Swift : 5.8
SwiftUIを使用
作成したアニメーション
実際のアニメーションでは、すぐに読み込まれてしまうため、以下の二点を追加しました。
- 100%になったら円に戻る。
- フェードアウト
実装コード
青:#3498DB 黄色:#F1C40F 赤:#E74C3C を使用してます。
import SwiftUI
struct HugginFaceProgressCircle: View {
// 各円の回転度数、トリムの開始位置、トリムの終了位置を保持するためのプロパティ
@State private var rotationDegrees: [Double] = [0, 0, 0]
@State private var startTrim: [CGFloat] = [0, 0, 0]
@State private var trimTo: CGFloat = 120.0 / 360.0 // 初期のトリム値(約33.3%)
@State private var shouldRotate = true // 円が回転しているかどうかを示すフラグ
@State private var opacity = 1.0 // テキストと円の不透明度
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() // 0.1秒ごとにトリガされるタイマー
// プログレスバーのパーセンテージ値と、それを更新するためのタイマー
@State private var percentage: Int = 0
let timer2 = Timer.publish(every: 0.3, on: .main, in: .common).autoconnect() // 0.3秒ごとにトリガされるタイマー
// 緑の円を拡大するためのフラグ(未使用)
@State private var expandGreenCircle = false
// ビューの本体
var body: some View {
ZStack { // 複数のビューを重ねて表示
Text("\(percentage)%") // 進行状況のパーセンテージを表示
.font(.system(size: 30)) // フォントサイズを設定
.opacity(opacity) // テキストの透明度を設定
.onReceive(timer2) { _ in // timer2の更新毎に実行される
if percentage < 100 {
percentage += Int.random(in: 1...5) // パーセンテージをランダムに増加
if percentage >= 100 {
percentage = 100 // パーセンテージが100を超えないように調整
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(.linear(duration: 1.0)) {
trimTo = 1.0 // パーセンテージが100%に達したら全ての円を完全に塗りつぶす
shouldRotate = false // パーセンテージが100%に達したら回転を停止
expandGreenCircle = true // パーセンテージが100%に達したら緑の円を拡大(現状未使用)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(.linear(duration: 0.4)) {
self.opacity = 0 // パーセンテージが100%に達した1秒後にテキストと円を透明にする
}
}
}
}
}
}
// 3つの円を描画
ForEach(0..<3) { index in
Circle()
.trim(from: 0, to: trimTo) // 円を一部切り取る
.stroke(lineWidth: 3) // 円の線の太さを設定
.frame(width: CGFloat(100 + 30 * index), height: CGFloat(100 + 30 * index)) // 円の大きさを設定
.foregroundColor(Color([ "Blue","Yellow","Red"][index])) // 円の色を設定
.rotationEffect(Angle.degrees(CGFloat(index*50)+rotationDegrees[index])) // 円を回転させる
.animation(shouldRotate ? Animation.linear(duration: 1).repeatForever(autoreverses: false) : .default) // 回転のアニメーション
.opacity(opacity) // 円の透明度を設定
}
}
.onAppear() {
startTrim = startTrim.map { _ in CGFloat.random(in: 0...1) } // ビューが表示されるときにトリムの開始位置をランダムに設定
}
.onReceive(timer) { _ in // timerの更新毎に実行される
if shouldRotate {
withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
// 内側の円ほど早く、外側の円ほど遅く回転します。
rotationDegrees = rotationDegrees.enumerated().map { index, degree in
degree + (50.0 * Double(1 - Double(index) * 0.2))
}
}
}
}
}
}
struct HugginFaceProgressCircle_Previews: PreviewProvider {
static var previews: some View {
HugginFaceProgressCircle()
}
}
コメントなしの実装コード
まとめ
Loading画面を自作するのも面白いですね。
今回のものでは、定期的に三本の線が重なるのが特にお気に入りです。
いいね、ブックマーク、フォローしていただけると勉強の励みになりますので是非お願いします。😉
追伸 --
Twitterで日々の学習風景を投稿してます。