SwiftUI でリストを実装中にハマったので書きます。
最小限のコードだけ抜き出しました。重要なところはList(またはForEach)の直後にswitch caseがある点です。
このように実装すると withAnimation
内でデータを追加してもアニメーションが発生しなくなります。
import SwiftUI struct ContentView: View { enum Value: Identifiable { case int(Int) case float(Float) var id: String { switch self { case let .int(value): "int(\(value))" case let .float(value): "float(\(value))" } } } @StateObject var viewModel = ViewModel() var body: some View { List(viewModel.values) { v in // Listの直下でswitch caseするとアニメーションしなくなる switch v { case let .int(value): Text("Int: \(value)") case let .float(value): Text("Float: \(value)") } } .listStyle(.plain) .safeAreaInset(edge: .bottom) { Button("Add Top") { withAnimation { viewModel.insert() } } } } } @MainActor final class ViewModel: ObservableObject { @Published var values: [ContentView.Value] = [.int(0)] func insert() { values.insert(Bool.random() ? .int(values.count) : .float(Float(values.count)), at: 0) } } #Preview { ContentView() }
動かない
ここで Add Top
ボタンを押すとデータが追加されるときにアニメーションが発生しません。
こんな感じに case 内のViewにidをつけても同じです。
List(viewModel.values) { v in // Listの直下でswitch caseするとアニメーションしなくなる switch v { case let .int(value): Text("Int: \(value)") .id(v.id) case let .float(value): Text("Float: \(value)") .id(v.id) } }
解決方法
こんな感じにList直下にswitch caseを入れずに一つのViewで囲みました(Groupでは駄目でした).
var body: some View { List(viewModel.values) { v in VStack { switch v { case let .int(value): Text("Int: \(value)") case let .float(value): Text("Float: \(value)") } } } .listStyle(.plain) .safeAreaInset(edge: .bottom) { Button("Add Top") { withAnimation { viewModel.insert() } } } }
するとこんなふうにちゃんとアニメーションするようになりました。
原因
原因はよくわかりません...
なんとなくList直下でswitch case したらViewの識別ができなくなって追加されたviewがわからなくなって reloadData
したような動作になってしまうんだろうなと考えています...
UIKitと違って細かい挙動を推測しにくいのがSwiftUIの難しいところですね...