Ideal Reality

興味の赴くままに

[SwiftUI]Apple Watchで画面遷移から戻った際にアニメーションが崩壊するのを防ぐ

WatchKitではアニメーションするのにパラパラ漫画のような画像を用意しなきゃいけなかったのが、SwiftUIを使うと簡単にApple Watchでもアニメーションをさせることができるようになりました。

ただ、時間の長いアニメーションをさせているときに画面遷移して戻ると崩壊してしまったので、その対処方法です。

Contents
スポンサーリンク

サンプル

とりあえず以下のサンプルコードをApple Watchで実行してみてください。

struct ContentView: View {
    
    @State var duration: TimeInterval = 30.0
    @State var endDate: Date?
    @State var value: TimeInterval = 0.0
    @State var showModal = false
    
    var body: some View {
        VStack {
            if let date = endDate {
                Text(date, style: .timer)
            } else {
                Text(String(format: "%.0f:%02d", duration / 60, Int(duration) % 60))
            }
            GeometryReader { geometry in
                Rectangle()
                    .foregroundColor(.gray)
                Rectangle()
                    .foregroundColor(.red)
                    .frame(width: geometry.size.width * CGFloat(value))
            }
            .frame(height: 2)
            .onAppear {
                if let remain = endDate?.timeIntervalSinceNow {
                    value = remain / duration
                    withAnimation(.linear(duration: remain)) {
                        value = 0
                    }
                } else {
                    value = 0
                }
            }
            .id(endDate)
            
            HStack {
                if endDate == nil {
                    Button("Start") {
                        endDate = Date(timeIntervalSinceNow: duration)
                    }
                    .foregroundColor(Color.green)
                } else {
                    Button("Stop") {
                        endDate = nil
                    }
                    .foregroundColor(Color.red)
                }
                Button("Modal") {
                    showModal = true
                }
            }
        }
        .padding()
        .sheet(isPresented: $showModal) {
            Button("Close") {
                showModal = false
            }
        }
    }
}

これ、iPhoneでは正常に動くのですが、現状手元でテストしているwatchOS 7.2だと画面遷移から戻った際に画像のようにアニメーションがおかしくなります。

対処方法

struct ContentView: View {
    
    @State var duration: TimeInterval = 30.0
    @State var endDate: Date?
    @State var value: TimeInterval = 0.0
    @State var showModal = false
    @State var animationSession = 0.0
    
    var body: some View {
        VStack {
            if let date = endDate {
                Text(date, style: .timer)
            } else {
                Text(String(format: "%.0f:%02d", duration / 60, Int(duration) % 60))
            }
            GeometryReader { geometry in
                Rectangle()
                    .foregroundColor(.gray)
                Rectangle()
                    .foregroundColor(.red)
                    .frame(width: geometry.size.width * CGFloat(value))
            }
            .frame(height: 2)
            .onAppear {
                if let remain = endDate?.timeIntervalSinceNow {
                    value = remain / duration
                    withAnimation(.linear(duration: remain)) {
                        value = 0
                    }
                } else {
                    value = 0
                }
            }
            .onDisappear {
                animationSession = floor(Date().timeIntervalSince1970 * 10)
            }
            .id(endDate)
            .id(animationSession)
            
            HStack {
                if endDate == nil {
                    Button("Start") {
                        endDate = Date(timeIntervalSinceNow: duration)
                    }
                    .foregroundColor(Color.green)
                } else {
                    Button("Stop") {
                        endDate = nil
                    }
                    .foregroundColor(Color.red)
                }
                Button("Modal") {
                    showModal = true
                }
            }
        }
        .padding()
        .sheet(isPresented: $showModal) {
            Button("Close") {
                showModal = false
            }
        }
    }
}

そこで、上記のコードのように、animationSessionという変数を作ってidに指定し、それをonDisappearで更新することで、画面遷移から戻った際に強制的にViewが再生成されるようにします。

ちなみにUUIDなどの実行時に毎回値が変わるものをidにしてしまうと、onAppearでアニメーションの更新をしている関係で無限ループに陥ります。それを回避するために、floor(Date().timeIntervalSince1970 * 10)を使うことで0.1秒以内で更新が走った時は同じidを吐くようにして、無限ループを阻止しています。

あまりいいやり方ではないですが、困った時は参考にしてみてください。一番いいのは将来のwatchOSのアップデートで改善されることなのですがね。

スポンサーリンク

コメント

投稿されたコメントはありません

名前

メールアドレス(任意)

コメント

関連する投稿

‘configure(withApplicationID:)’ is deprecatedと言われた

SwiftのDecimal(string:)がどれだけ使えるか試してみた

NSTextFieldにadjustsFontSizeToFitWidthがないから自作する

[SwiftUI]EnvironmentObjectをウィンドウごとに別々のインスタンスにしたい

[Swift]Dictionaryに順番を持たせたい

[iOS, Swift]で画面回転時のアニメーションを無効化する