ARTICLE AD BOX
It is generally difficult to animate a change in height for a List row smoothly. However, a possible workaround is to use a DisclosureGroup.
When a DisclosureGroup with native styling is used in a List, it navigates quite smoothly. The way it works is actually to add a new row to the list when expanded. To use your own styling, a custom DisclosureGroupStyle can be used. By using the same technique of adding an extra row for the expanded content, the animation works quite smoothly.
Some notes:
Tracking the expanded state can be handled by the DisclosureGroup. The function makeBody of the custom style should return two rows when the content is expanded. This is done by not using a VStack as outer container for the content. The vertical spacing for the expanded content can be reduced by applying custom .listRowInsets. The list row separator above the expanded note can be hidden. The alignment of the separator below the expanded note can be controlled using an .alignmentGuide.Here is how your example can be adapted to work this way:
struct MyDisclosureGroupStyle: DisclosureGroupStyle { func makeBody(configuration: Configuration) -> some View { HStack(spacing: 12) { configuration.label Spacer() Button { withAnimation { configuration.isExpanded.toggle() } } label: { Image(systemName: "note.text") .imageScale(.medium) } .buttonStyle(.plain) } .padding(.vertical, 8) if configuration.isExpanded { configuration.content .listRowInsets(.init(top: 0, leading: 16, bottom: 10, trailing: 16)) .padding(.bottom, 8) .listRowSeparator(.hidden, edges: .top) .alignmentGuide(.listRowSeparatorLeading) { dim in 0 } } } } struct ContentView: View { private let items: [HistoryItem] = [ HistoryItem(id: UUID(), title: "Client A - Morning shift", note: "Short note for entry 1."), HistoryItem(id: UUID(), title: "Client B - Field visit", note: "Longer note for entry 2 so the row expands more than the others."), HistoryItem(id: UUID(), title: "Client C - Training", note: nil), HistoryItem(id: UUID(), title: "Client D - Support", note: "Another note for entry 4."), HistoryItem(id: UUID(), title: "Client E - Wrap up", note: "Final note for entry 5.") ] var body: some View { NavigationStack { List { ForEach(items) { item in if let note = item.note { DisclosureGroup { Text(note) .font(.subheadline) .foregroundStyle(.secondary) .padding(10) .background(.background.secondary, in: .rect(cornerRadius: 8)) } label: { Text(item.title) .font(.headline) } .disclosureGroupStyle(MyDisclosureGroupStyle()) } else { Text(item.title) .font(.headline) .padding(.vertical, 8) } } } .listStyle(.plain) .navigationTitle("History") } } }
