Reputation: 309
I am working on a project involving SwiftUI lists. One of my views contains a list with relatively complex list items. To reduce code complexity I have created a subview for each item, like this (Simplified but reproduces the problem):
import SwiftUI
struct SubView: View {
var val: String
var body: some View {
Text(val)
}
}
struct TestView: View {
@State var objects = ["A", "B", "C", "D", "E"]
var body: some View {
List(objects, id: \.self) { ob in
SubView(val: ob)
.onTapGesture {
objects.removeAll { $0 == ob }
}
}
.animation(.default, value: objects)
}
}
#Preview {
TestView()
}
This code works as intended, expect for the animations. When an item is clicked, the last item in the list fades (even if some other item is deleted) and then the list changes without animation to the correct list. This code, however, does work as intended:
import SwiftUI
struct TestView: View {
@State var objects = ["A", "B", "C", "D", "E"]
var body: some View {
List(objects, id: \.self) { ob in
Text(ob)
.onTapGesture {
objects.removeAll { $0 == ob }
}
}
.animation(.default, value: objects)
}
}
#Preview {
TestView()
}
These views are effectively the same, so I would expect them to both have functional animations, but the first does not. Why does this happen? How can I use something similar to the first code sample while maintaining the animations?
Upvotes: 1
Views: 253
Reputation: 1
I have come across this issue using (iOS 18.1) and it brought me here where I was not able to propagate the animation from the cell level to List (being parent). I believe the List
doesn't observe the animation directly. Animations triggered by local states are not propagated to the parent List.
One solution is to replace List with a ForEach wrapped in a ScrollView and LazyVStack to gain better control over animations.
struct ScrollViewWithAnimation: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 20) {
ForEach(1...20, id: \.self) { index in
AnimatedTextView(index: index)
}
}
.padding()
}
}
}
struct AnimatedTextView: View {
let index: Int
@State private var isVisible: Bool = false
var body: some View {
Text("Item \(index)")
.font(.largeTitle)
.foregroundColor(.blue)
.scaleEffect(isVisible ? 1.0 : 0.5) // Scale animation
.opacity(isVisible ? 1.0 : 0.3) // Fade-in effect
.animation(.easeInOut(duration: 0.5), value: isVisible) // Smooth animation
.onAppear {
isVisible = true
}
}
}
Upvotes: 0
Reputation: 20459
I only see this happening when running as a Preview.
Try running it in a simulator or on a real device. When running on an iPhone 15 simulator with iOS 17.2 (Xcode 15.1), the animations for both cases look identical to me: the item being deleted fades out at the same time as the list slides up (with animation) to close the gap. No other fading can be seen.
Previews are not always reliable for testing animations. Simulators are much more reliable.
Upvotes: 2
Reputation: 8866
I guess this is another SwiftUI bug. At first glance, I thought it was about Identifiable
. However, after modifying the object model, it makes no difference. You can fix it by nest list element within any component, such as:
HStack {
SubView(val: ob)
.onTapGesture {
objects.removeAll { $0 == ob }
}
}
Upvotes: 2