Preselecting a (dynamic) item in NavigationSplitView

368 views Asked by At

I'm using NavigationSplitView to structure the user interface of my (macOS) app like this:

struct NavigationView: View {
    
    @State private var selectedApplication: Application?
    
    var body: some View {
        NavigationSplitView {
            ApplicationsView(selectedApplication: $selectedApplication)
        } detail: {
            Text(selectedApplication?.name ?? "Nothing selected")
        }
    }
}

The sidebar is implemented using ApplicationsView that looks like this:

struct ApplicationsView: View {
    
    @FetchRequest(fetchRequest: Application.all()) private var applications
    @Binding var selectedApplication: Application?
    
    var body: some View {
        List(applications, selection: $selectedApplication) { application in
            NavigationLink(value: application) {
                Text(application.name)
            }
        }
        
        // This works, but looks a bit complicated and... ugly
        .onReceive(applications.publisher) { _ in
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                if selectedApplication == nil {
                    selectedApplication = applications.first
                }
            }
        }

        // This also does not work, as the data is not yet available
        .onAppear {
            selectedApplication = applications.first
        }
    }
}

I'm currently preselecting the first Application item (if it exists) using the shown onReceive code, but it looks complicated and a bit ugly. For example, it only works properly when delaying the selection code.

Is there a better way to achieve this?

Thanks.

1

There are 1 answers

5
Ashley Mills On BEST ANSWER

How about just setting the selectedApplication using the task modifier with an id as follows:

struct ApplicationsView: View {
    
    @FetchRequest(fetchRequest: Application.all()) private var applications
    @Binding var selectedApplication: Application?
    
    var body: some View {
        List(applications, selection: $selectedApplication) { application in
            NavigationLink(value: application) {
                Text(application.name!)
            }
        }
        .task(id: applications.first) {
            selectedApplication = applications.first
        }
    }
}

the task is fired when the view is first displayed, and when the id object is updated, so this works without introducing a delay

enter image description here