swiftuiswiftui-listswiftui-navigationstack

SwiftUI List selection behaviour Preview vs Simulator


I am having trouble understanding why SwiftUI list selection behaviour is different between the Previews and the actual app running when pushing a detail view.

Here is the app view

import SwiftUI

@main
struct SwiftUIListApp: App {
    var body: some Scene {
        WindowGroup {
            ListView()
        }
    }
}

and here is my simple list

import SwiftUI

struct ListItem: Identifiable, Hashable {
    let title: String
    var id: String { title }
}

@Observable
final class ListViewModel {
    private(set) var items: [ListItem] = []
    
    @MainActor func loadData() async {
        items = (1...100).map { ListItem(title: "\($0)") }
    }
}

struct ListView: View {
    @State private var viewModel = ListViewModel()
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.items) { item in
                    NavigationLink(value: item) {
                        Text(item.title)
                    }
                }
            }
            .navigationTitle("SwiftUI List")
            .navigationDestination(for: ListItem.self) { item in
                ListDetailView(item: item)
            }
        }
        .task {
            await viewModel.loadData()
        }
    }
}

struct ListDetailView: View {
    let item: ListItem
    
    var body: some View {
        Text("Detail view \(item.title)")
            .navigationTitle("Detail")
            .navigationBarTitleDisplayMode(.inline)
    }
}

#Preview {
    ListView(viewModel: ListViewModel())
}

When I select a row in the preview, the row gets selected and the detail view is pushed. When I navigate back to the list the row gets de-selected when the list view appears. This is similar behaviour to UITableViewController and is exactly what I want. (https://developer.apple.com/documentation/uikit/uitableviewcontroller/1614758-clearsselectiononviewwillappear)

However when I run the app in the simulator/device, the row gets deselected immediately before the push. The list view itself does not seem to re-draw because scroll position is maintained correctly.


Solution

  • Found the solution after playing around with Apples demo app https://developer.apple.com/documentation/swiftui/bringing_robust_navigation_structure_to_your_swiftui_app

    Added this to the view model

    var itemPaths: [ListItem] = []
    

    And updated the NavigationStack

    NavigationStack(path: $viewModel.itemPaths) { ... }
    

    Still not sure why the preview behaves differently but I take it.