9
4

More than 1 year has passed since last update.

[SwiftUI]Hugginface Loading画面のAnimationを再現する

Last updated at Posted at 2023-07-19

HuggingFaceのDemoを起動すると、3色のラインがProgressの周りをぐるぐる回るアニメーションが表示される。これがとても可愛らしいのでSwiftUIで再現してみました。

Screen Recording 2023-07-19 at 10.34.04 (1).gif

実際のリンク
https://huggingface.co/spaces/wyysf/GenMM

開発環境

Xcode : 14.3

Swift : 5.8

SwiftUIを使用

作成したアニメーション

実際のアニメーションでは、すぐに読み込まれてしまうため、以下の二点を追加しました。

  • 100%になったら円に戻る。
  • フェードアウト

Screen Recording 2023-07-18 at 11.51.20 2.gif

実装コード

青:#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で日々の学習風景を投稿してます。

@Ren_yello

9
4
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
9
4