1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LazyVGridとかLazyVStackのカラムにアニメーションをつける時の注意点

Posted at

はじめに

最近はSwiftUIを触る機会が多いのですが、Table形式やCollection形式のViewを作るときは、Lazy〇〇を使うようにしています。
そんなこんなやっていた時に、LazyVGridをスクロールした時にバグが発生する問題に直面し、若干ハマったので解決方法を記事にしてみました。
ちなみにですが、iOS15のみ対応のアプリだとwarningが出るので、このバグに直面することは少ないんじゃないかと思います!

バグの正体

スクロールした時にセルの描画が遅れたような感じになってしまいます。
バグ.gif
この時のコードは下記です。
このViewでは、各Cellをタップした時に、青からオレンジに背景色を変えるところをアニメーションにしています。

import SwiftUI

struct ContentView: View {
    
    @State private var selectedColumns = Set<Int>()
    
    let columns: [GridItem] = Array(
        repeating: .init(.flexible()),
        count: 5
    )
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns) {
                ForEach((1...100), id: \.self) { index in
                    Rectangle()
                        .frame(width: 60, height: 60)
                        .foregroundColor(selectedColumns.contains(index) ? .orange : .blue)
                        .animation(.easeInOut(duration: 0.3))
                        .onTapGesture {
                            switchSelected(index: index)
                        }
                }
            }
        }
    }
    
    func switchSelected(index: Int) {
        if selectedColumns.contains(index) {
            selectedColumns.remove(index)
        } else {
            selectedColumns.insert(index)
        }
    }
}

原因

結論から書くと、どのプロパティ状態によってアニメーションが行われるかが明確化されていないためにこの問題が起こります。
iOS15からは非推奨になっているメソッドanimation(_:value:)は、どのプロパティの変化によってアニメーションが起きるかが明確になっていません。

上記のコードで言うと.animation(.easeInOut(duration: 0.3))が該当します。

解決

iOS15から非推奨になった上記のメソッドの代わりに、animation(_:value:)を利用することです。(推奨になっただけで以前のOSでも使える)
先ほどのメソッドとの違いは、valueという第2引数が必要になったことです。

こちらのメソッドは、valueに入れたプロパティが変更されたときのみViewのanimationが走るという効果があります。
なので、valueには、animationと紐づけたい処理を入れるのが正しいということになります。

今回の場合、selectedColumnsにタップされたGridのIndexが追加された時にanimationをキックしたいので、
上記コードを
.animation(.easeInOut(duration: 0.3)) -> .animation(.easeInOut(duration: 0.3), value: selectedColumns)
というように変更してあげればOKです。

ちゃんと動くようになりました!
正常.gif

FYI

解決方法は実はもう1つあります。

withAnimation(_:_:)を利用する方法です。
利用方法はanimationをキックしたい処理のところをwithAnimation(_:_:)のブロックの中に書いてあげる感じです。

.onTapGesture {
    withAnimation(.easeInOut(duration: 0.3)) {
        switchSelected(index: index)
    }
}

UIKitのアニメーションっぽい感じなので、UIKitに慣れている場合はこちらの方が直感的な気がします。

おわりに

最初にお伝えした通り、今回のバグの原因となるコードはiOS15〜は非推奨なのでそもそも使わないのが理想ですね。
ただ、iOS14などをサポートしている場合、warningが出ないので気づかないうちに使っているということもあるあるです。

そう言ったケースでは「アニメーションを明示的に指定する」ことでこのような問題が解決できることがわかりました!
読んでいただきありがとうございました🙏

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?