I have a rather simple premise: I have a ForEach with data items inside that are NSObjects (actually they inherit and are more specific) let's call them TestClass
. When a new item appears (or disappears) from the data, I want to use a scale transition to scale the view in-/or out.
However, here is the issue: whenever the array changes in any way (even if it is beign set to the same value as it already has, all items are being scaled for a short time, as if dissappearing and appearing rapidly.
How can I achieve my desired effect?
Code to reproduce:
class TestClass: NSObject {
var id: String
init(id: String) {
self.id = id
}
/* does not help...
static func == (lhs: TestClass, rhs: TestClass) -> Bool {
return lhs.id == rhs.id
}
override public var hash: Int {
return id.hash
}*/
}
struct TestView: View {
@State var test2: [TestClass] = [
TestClass(id: "1")
]
var body: some View {
HStack(spacing: 20) {
ForEach(test2, id: \.self) { _ in
Color.red.frame(width: 100, height: 100).transition(.scale)
}
.animation(.bouncy(duration: 0.4), value: test2)
.onAppear {
Task {
try? await Task.sleep(seconds: 5)
print("RESET")
print("before: \(test2)")
withAnimation {
self.test2 = [
TestClass(id: "1"),
TestClass(id: "2"),
]
}
print("after: \(test2)")
try? await Task.sleep(seconds: 5)
print("RESET2")
withAnimation {
self.test2 = [
TestClass(id: "1"),
TestClass(id: "2"),
TestClass(id: "3"),
]
}
}
}
This problem is all about identity.
If TestClass
could be changed to a struct then it works:
struct TestClass: Hashable {
// ...
}
Alternatively, if TestClass
must be a class then just change the ForEach
to use id: \.id
:
ForEach(test2, id: \.id) { _ in
Better still, TestClass
could conform to Identifiable
:
class TestClass: NSObject, Identifiable {
// ...
}
Then the ForEach
can be simplified:
ForEach(test2) { _ in