diff --git a/apps/iOS/Components/PerformerRecordingsSection.swift b/apps/iOS/Components/PerformerRecordingsSection.swift index 0286ed9..a50cb6d 100644 --- a/apps/iOS/Components/PerformerRecordingsSection.swift +++ b/apps/iOS/Components/PerformerRecordingsSection.swift @@ -41,7 +41,16 @@ struct PerformerRecordingsSection: View { controlsBar .padding(.horizontal, ApproachNoteTheme.spacingXL) - LazyVStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { + // Eager VStack, not LazyVStack — same reason as the song-detail + // RecordingsSection. A lazy outer stack whose children each hold a + // horizontal ScrollView + LazyHStack makes SwiftUI's lazy-layout + // pass fail to settle on long lists: on iPad, Louis Armstrong + // (3231 recordings) pinned the main thread at 100% indefinitely + // inside LazyVStackLayout.finalPlacement -> + // LazyHStackLayout.initialPlacement. This stack holds one row per + // group (decade or song title), not one per recording, so building + // it eagerly is cheap. + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { if !filteredRecordings.isEmpty { ForEach(groupedRecordings, id: \.groupKey) { group in groupAccordion(group: group) diff --git a/apps/iOS/Components/RecordingsSection.swift b/apps/iOS/Components/RecordingsSection.swift index 0da15b2..9b5ca08 100644 --- a/apps/iOS/Components/RecordingsSection.swift +++ b/apps/iOS/Components/RecordingsSection.swift @@ -76,7 +76,17 @@ struct RecordingsSection: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, ApproachNoteTheme.spacingXL) - LazyVStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { + // Deliberately an eager VStack, not a LazyVStack. Each child is a + // collapsed accordion header (a Button with two Texts) and, when + // open, a horizontal ScrollView + LazyHStack of cards. Nesting that + // inside a lazy stack makes SwiftUI's lazy-layout pass fail to reach + // a fixed point on long lists: on iPad, "All of Me" (701 shell rows) + // spun the main thread at 100% forever inside + // LazyVStackLayout.finalPlacement -> LazyHStackLayout.initialPlacement, + // so no touch was delivered and cover art never loaded. Making the + // outer stack eager settles it, and costs little — this stack holds + // one row per group, not one per recording. + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { if !filteredRecordings.isEmpty { ForEach(groupedRecordings, id: \.groupKey) { group in groupAccordion(group: group)