1

Edit:

I found a solution but I would really like to know why it behaves this way as I got the solution by simply trial and error.

The solution I found is to add .animation(.default, value: user.times) modifier to the list in the child view. Why it works this way, I have no Idea.

I have this weird problem where when I pass a binding to a child view, changes inside this child view are not animated except for .onDelete modifier.

Here is a simplified code that produces this problem:

Observable(ViewModel) and model struct:

extension ContentView {
    class ViewModel: ObservableObject {
        @Published var users = [User]()

        func createUser() {
            users.append(User(times: []))
        }
    }

    struct User: Equatable {
        let name = UUID().uuidString
        var times: [Date]
    }
}

ContentView: (Parent view)

struct ContentView: View {
    @StateObject var vm = ViewModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(vm.users.indices, id: \.self) { index in
                    NavigationLink {
                        UserView(user: $vm.users[index])
                    } label: {
                        Text(vm.users[index].name)
                    }
                }
                .onDelete { offsets in
                    vm.users.remove(atOffsets: offsets)
                }
                Button("Add") {
                    withAnimation {
                        vm.createUser()
                    }
                }
            }
            .navigationTitle("Parent View")
        }
    }
}

UserView (Child View):

struct UserView: View {
    @Binding var user: ContentView.User

    var body: some View {
        List {
            ForEach(user.times.indices, id: \.self) { index in
                Text(user.times[index].description)
            }
            .onDelete { offsets in
                user.times.remove(atOffsets: offsets)
            }
            Button {
                withAnimation {
                    user.times.append(Date())
                }
            } label: {
               Text("Add Time")
            }

        }
        .navigationTitle("Child View")
    }
}

Notice in below image that the parent view having almost exactly the same code except for binding is working and animating as intended, and notice the child view .onDelete modifier is also animating as it intended but when adding to the list of child view, animation are not working.

Thanks! enter image description here

Yasser
  • 27
  • 1
  • 4
  • The problem is that the parent view's list is reloaded whenever the child view appends a new date into its`times` array. I don't think there's a way to animate adding items to the subview's list. Whenever a list item changes, SwiftUI reloads the entire list which in your case would cause both the parent and child view to reload. – Anwuna Jan 05 '22 at 19:07
  • Thanks @Anwuna! I actually found a way to fix but I don't really why it works that way. I'll update my question to see if people can get why it behaves this way. – Yasser Jan 05 '22 at 19:53

1 Answers1

0

I am fairly sure the problem is in the use of .indices, since the id: \.self is now referencing the Int in the indices array [Int] not the User object in the [User] array,

If i recall correctly a solution is to use enumerated() in the following way:

ForEach(Array(array.enumerated()), id: \.offset) { index, element in
  // ...
}
jalone
  • 1,953
  • 4
  • 27
  • 46