はじめに
最近はSwiftUIを触る機会が多いのですが、Table形式やCollection形式のViewを作るときは、Lazy〇〇を使うようにしています。
そんなこんなやっていた時に、LazyVGridをスクロールした時にバグが発生する問題に直面し、若干ハマったので解決方法を記事にしてみました。
ちなみにですが、iOS15のみ対応のアプリだとwarning
が出るので、このバグに直面することは少ないんじゃないかと思います!
バグの正体
スクロールした時にセルの描画が遅れたような感じになってしまいます。
この時のコードは下記です。
この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です。
FYI
解決方法は実はもう1つあります。
withAnimation(_:_:)
を利用する方法です。
利用方法はanimationをキックしたい処理のところをwithAnimation(_:_:)
のブロックの中に書いてあげる感じです。
.onTapGesture {
withAnimation(.easeInOut(duration: 0.3)) {
switchSelected(index: index)
}
}
UIKitのアニメーションっぽい感じなので、UIKitに慣れている場合はこちらの方が直感的な気がします。
おわりに
最初にお伝えした通り、今回のバグの原因となるコードはiOS15〜は非推奨なのでそもそも使わないのが理想ですね。
ただ、iOS14などをサポートしている場合、warning
が出ないので気づかないうちに使っているということもあるあるです。
そう言ったケースでは「アニメーションを明示的に指定する」ことでこのような問題が解決できることがわかりました!
読んでいただきありがとうございました🙏