Animating text change and placement at once in ForEach clause

56 Views Asked by At

I have an array that holds some data, that is changing by a button click. One of the properties of that data is a number. I want to make a view that will show the numbers in the array and with every button click, they will be resorted by the new number that will be updated. So every time the user clicks on the button, the array will get new values and the views will animate both at once:

  • The value in the Text to that new number.
  • The position of the view based on its value in the sorted array.

A simple example that demonstrate the problem (not the original code, so keep in mind that every data element in the datas array holds much more than just the number):

struct ContentView: View {
    @State private var datas = (0...9).map { Data(id: $0, value: $0) }
    
    var body: some View {
        VStack {
            Button("Click Me!") {
                withAnimation(.easeInOut(duration: 3)) {
                    randomizeDataValues()
                }
            }
            VStack {
                ForEach(datas, id: \.id) { data in
                    BulletAndText(num: data.value)
                }
            }
        }
        .padding()
        .transition(.slide)
    }
    
    struct Data: Identifiable, Equatable {
        var id: Int
        var value: Int
    }
    
    func randomizeDataValues() {
        for i in 0..<datas.count {
            datas[i].value = Int.random(in: 1..<datas.count)
        }
        datas.sort(by: { d1, d2 in d1.value < d2.value })
    }
}

struct BulletAndText: View {
    let num: Int
    
    var body: some View {
        GroupBox {
            HStack {
                Circle()
                    .frame(height: CGFloat(num))
                Text(String(num))
            }
        }
    }
}

This is a clean testing code created from many tries I've already made. I tried working with .id() and with .matchedGeometryEffect() to solve the issue but I could not find a way to tell Swiftui that corresponding Text() views should be connected. My guess is that the problem is with the ForEach clause since that making a conditional (if-else) clause instead of it with the same view and with .matchedGeometryEffect() works as intended.

This gif is showing the code in action.

The gif

important remarks about it:

  • The text will do the transition only if it will be with the same text (in the gif, the number 3 is doing the transition and the others do not).
  • The bullets near the text are changing their values and still doing the transition.

Thanks a lot for the help.

0

There are 0 best solutions below