7

I am trying to animate the expansion of cells in a swiftUI list when the user taps on the cell. However the animation is faulty.

I came across this answer (https://stackoverflow.com/a/60873883/13296730) to help with the animation however it requires already knowing the expected height of the cell after it is expanded. In my case this would depend on the data in note.text:

List{
    ForEach(notes){ note in
        VStack{
            HStack{
                Text(note.title)
                    .fontWeight(.bold)
                    .foregroundColor(.gray)
                Spacer()
                Image(systemName: "chevron.right.circle")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.gray)
                    .frame(width: 20, height: 20)
            }
            .padding(.vertical)
            .contentShape(Rectangle())
            .onTapGesture{
                selectedNote = note
            }
            if isSelected(note) {
                Text(note.text)
            }
        }
        .padding([.bottom, .leading, .trailing], 20)
        .background(
            RoundedRectangle(cornerRadius: 25, style: .continuous)
                .foregroundColor(isSelected(noteSection) ? .red : .clear)
        )
        .animation(.default)
    }
    .onMove(perform: move)
}

As you can see from the code above, I need to use a list so that I can make use of the .onMove functionality.

Is there a way to get the same smooth list animations in the linked answer but for dynamic expanded sizes?

Thanks.

santi.gs
  • 514
  • 3
  • 15
  • I am struggling with the exact same challenge... disheartened to see a year old question unanswered. – spentag Feb 28 '22 at 21:26
  • @spentag maybe this approach might work for you: https://stackoverflow.com/a/60890312/1016508 – iMaddin Mar 28 '22 at 08:55

2 Answers2

4

I struggled with this for a while, and visited a bunch of the existing solutions on Stack Overflow, but they all seemed too complicated, especially now in 2023.

What I eventually stumbled on was instead of putting the content inside of a List you can just put it inside of a ScrollView. Then you will get nice, clean animations without any weird hacks with custom height modifiers or having to know the height of your container before hand. You'll just need to add a bit of extra styling to get it to look as nice as a List does by default.

struct Cell: View {
    @ObservedObject var item: Item
    @State var showExtra = false

    var body: some View {
        VStack {
            Text("tap me")
                .onTapGesture {
                    withAnimation{
                        showExtra.toggle()
                    }
                }
            if showExtra {
                Text("I'm extra text")
            }
        }
    }
}

struct ContenetView: View {
    var items = [ ... ] // your items
    var body: some View {
        ScrollView {
            ForEach(items) { item in
                Cell(item: item)
            }
        }
    }
}
jcaruso
  • 950
  • 11
  • 19
  • Agree, I looked a bunch as well. It's crazy that in 2023 List rows still can't be easily expanded like this. SwiftUI is suppose to "work" with the expected case of the code and it falls down badly in this area. Thanks for the pointer for using a custom ScrollView. – Gerry Shaw Jul 02 '23 at 06:40
0

im still new to SwiftUi but managed to get something working by using the below code. I can't get multiple of the listRow's to animation if I just ForEach loop over list row but in this example but this approach does seam to work. Maybe someone could take it the next step to work out why the animations work in this method.

I feel its something todo with the list not knowing which row is which or something, hence me adding the id property. I also couldn't get any animations on anything in the view until the id property was changed. hence the onChange modifier.

love some feedback and ideas on it.

struct testViewTwo: View {

@State private var id1 = UUID()
@State private var id2 = UUID()

var body: some View {
    List {
        listRow(id: $id1)
        listRow(id: $id2)
    }
    .listStyle(.plain)
}}


struct listRow: View {
@Binding var id: UUID
@State private var isShowingRow: Bool = false
@State private var animateView: Bool = false

var body: some View {
    Group {
        VStack (alignment: .leading) {
            Text("\(id.uuidString)")
                .font(.caption2)
            Text("Animation test")
            Button {
                withAnimation {
                    //MARK: tirggers animation OF view
                    id = UUID()
                }
                isShowingRow.toggle()
            } label: {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundColor(animateView ? .blue : .red)
                    .animation(.easeInOut(duration: 2), value: animateView)
            }
            .buttonStyle(.bordered)
            VStack {
                if isShowingRow {
                    Text("Hello, world!")
                    Text("Hello, world!")
                    Text("Hello, world!")
                }
                else {
                    EmptyView()
                }
            }
        }
        .padding()
    }
    .id(id)
    .onChange(of: id) { newValue in
        //MARK: triggers animations IN view
        animateView.toggle()
    }
}}