0

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.

Niels Mouthaan
  • 1,010
  • 8
  • 19
  • `onAppear` works fine for this and the fetched results are available then – malhal Jan 11 '23 at 22:33
  • Putting `selectedApplication = applications.first` in `onAppear` does not work. Putting it in `task` (see the accepted answer), however, does work. – Niels Mouthaan Jan 12 '23 at 11:10

1 Answers1

0

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

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • Thanks. I should have mentioned I had already tried this. Unfortunately, this doesn't work. Very likely because the data isn't available yet when `onAppear` is called. If I explicitly add a delay of 1 second before running `selectedApplication = applications.first` it does work, though. – Niels Mouthaan Jan 11 '23 at 14:00
  • OK, I've updated my answer which I think should now work (at least, it did in my test app) – Ashley Mills Jan 11 '23 at 14:15
  • Thanks again. This works, but still not without the (0.01) delay. Without it, the app is printed in the detail view but isn't selected in the sidebar. The app isn't printed in the detail view anymore when opening the sidebar. Hence, without delay, things work unreliably. – Niels Mouthaan Jan 11 '23 at 15:07
  • That's odd - I wrote a test app to try this out that deletes all the data in the app `init`, and re-loads after a second, and this all works as expected. The first "application" is selected when the data is loaded (see video attached to my answer) - the other selections are me clicking the sidebar. Is there something else you're doing in the app that could be causing this issue? – Ashley Mills Jan 11 '23 at 15:29
  • 1
    Appreciate the effort! It seems I had some code elsewhere that was breaking this behavior. It now works correctly. Thanks! – Niels Mouthaan Jan 11 '23 at 22:12