swiftswiftuicore-datanavigationsplitview

Preselecting a (dynamic) item in NavigationSplitView


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.


Solution

  • 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