ボクココ

少人数でのサービス開発運営に関するテックブログ

SwiftUI でのローディングアイコン表示

ども、@kimihom です。

SwiftUI を使ってローディングアイコン(くるくる)を表示させようとした時、まず真っ先に GIF を使おうと思うだろうが、残念ながら GIF サポートは現状ない。何かしらのライブラリを使ったり、自前で動かす実装が必要になる。

ということで、現状の最適な方法としては GIF に代わる自前実装となるのがベストだろう。

参考サイト

まず以下の StackOverflow を参考にさせてもらった。

Activity indicator in SwiftUI - Stack Overflow

ただ、この通りに実装すると最新の iOS 15 だと正しく動作しない部分があったので補足として本記事を記す。

SwiftUI コード

まずは改善版のコードを記しておく。

import Foundation
import SwiftUI

struct ActivityIndicator: View {
    @State private var isAnimating: Bool = false

    var body: some View {
        GeometryReader { (geometry: GeometryProxy) in
            ForEach(0..<5) { index in
                Group {
                    Circle()
                        .frame(width: geometry.size.width / 5, height: geometry.size.height / 5)
                        .scaleEffect(calcScale(index: index))
                        .offset(y: calcYOffset(geometry))
                }.frame(width: geometry.size.width, height: geometry.size.height)
                    .rotationEffect(!self.isAnimating ? .degrees(0) : .degrees(360))
                    .animation(Animation
                                .timingCurve(0.5, 0.15 + Double(index) / 5, 0.25, 1, duration: 1.5)
                                .repeatForever(autoreverses: false), value: UUID())
            }
        }
        .aspectRatio(1, contentMode: .fit)
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                self.isAnimating = true
            }
        }
    }

    func calcScale(index: Int) -> CGFloat {
        return (!isAnimating ? 1 - CGFloat(Float(index)) / 5 : 0.2 + CGFloat(index) / 5)
    }

    func calcYOffset(_ geometry: GeometryProxy) -> CGFloat {
        return geometry.size.width / 10 - geometry.size.height / 2
    }

}

struct ActivityIndicator_Previews: PreviewProvider {
    static var previews: some View {
        ActivityIndicator()
            .frame(width: 200, height: 200)
            .foregroundColor(.orange)
    }
}

初回ロードがぐちゃぐちゃになる

この StackOverflow のコメントにもあるように、初回ロードでアイコンが左上に向かっていってしまうようなアニメーションとなってしまう。そのため、一時的に処理を待たせる実装で解決している。 asyncAfter の利用している行が該当するコードだ。

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    self.isAnimating = true
}

UUID の指定のない警告

新しい iOS では Animation に UUID の指定が必要のようで、追加する必要があった。

.animation(Animation
    .timingCurve(0.5, 0.15 + Double(index) / 5, 0.25, 1, duration: 1.5)
    .repeatForever(autoreverses: false), value: UUID())

終わりに

SwiftUI ではまだ「こんなことやりたい」をさっと解決できるようなものが見つからなかったりする。最初は慣れるまで大変だし、iOS アップデートの度にどんどんと UI の追加がされていく。

その成長を楽しんでいくマインドで、今後も SwiftUI と付き合っていこう。

SwiftUI 徹底入門

SwiftUI 徹底入門

Amazon