9

I'm playing around with the new navigation API's offered in ipadOS16/macOS13, but having some trouble working out how to combine NavigationSplitView, NavigationStack and NavigationLink together on macOS 13 (Testing on a Macbook Pro M1). The same code does work properly on ipadOS.

I'm using a two-column NavigationSplitView. Within the 'detail' section I have a list of SampleModel1 instances wrapped in a NavigationStack. On the List I've applied navigationDestination's for both SampleModel1 and SampleModel2 instances.

When I select a SampleModel1 instance from the list, I navigate to a detailed view that itself contains a list of SampleModel2 instances. My intention is to navigate further into the NavigationStack when clicking on one of the SampleModel2 instances but unfortunately this doesn't seem to work. The SampleModel2 instances are selectable but no navigation is happening.

When I remove the NavigationSplitView completely, and only use the NavigationStack the problem does not arise, and i can successfully navigate to the SampleModel2 instances.

Here's my sample code:


import SwiftUI

@main
struct testingnavigationApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// Sample model definitions used to trigger navigation with navigationDestination API.
struct SampleModel1: Hashable, Identifiable {
    let id = UUID()
    
    static let samples = [SampleModel1(), SampleModel1(), SampleModel1()]
}

struct SampleModel2: Hashable, Identifiable {
    let id = UUID()
    
    static let samples = [SampleModel2(), SampleModel2(), SampleModel2()]
}


// The initial view loaded by the app. This will initialize the NavigationSplitView
struct ContentView: View {
    
    enum NavItem {
        case first
    }
    
    var body: some View {
        NavigationSplitView {
            NavigationLink(value: NavItem.first) {
                Label("First", systemImage: "house")
            }
        } detail: {
            SampleListView()
        }
    }
}

// A list of SampleModel1 instances wrapped in a NavigationStack with multiple navigationDestinations
struct SampleListView: View {
    
    @State var path = NavigationPath()
    @State var selection: SampleModel1.ID? = nil
    
    var body: some View {
        NavigationStack(path: $path) {
            List(SampleModel1.samples, selection: $selection) { model in
                NavigationLink("\(model.id)", value: model)
            }
            .navigationDestination(for: SampleModel1.self) { model in
                SampleDetailView(model: model)
            }
            .navigationDestination(for: SampleModel2.self) { model in
                Text("Model 2 ID \(model.id)")
            }
        }
    }
}

// A detailed view of a single SampleModel1 instance. This includes a list
// of SampleModel2 instances that we would like to be able to navigate to
struct SampleDetailView: View {
    
    var model: SampleModel1
    
    var body: some View {
        Text("Model 1 ID \(model.id)")
        
        List (SampleModel2.samples) { model2 in
            NavigationLink("\(model2.id)", value: model2)
        }
    }
}




struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

thiezn
  • 1,874
  • 1
  • 17
  • 24
  • I'm having a similar problem in beta 3 on macOS (it works okay for me on iPad as per Asperi's answer). I had it working in beta 2 but the move to b3 seems to have broken it. – robuk Jul 19 '22 at 15:01
  • See this answer https://stackoverflow.com/a/76230910/8617744 it will solve this nested navigation problem of `NavigationStack` and `NavigationPath` – Md. Yamin Mollah May 11 '23 at 19:11

3 Answers3

0

I removed this unclear ZStack and all works fine. Xcode 14b3 / iOS 16

//            ZStack {   // << this !!
                SampleListView()
//            }

demo

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks @asperi, it seems the same code indeed works properly on ipadOS. I'm having the issue specifically on macOS Ventura. Regarding the ZStack, the beta 3 release notes mention the following: "Conditional views in columns of NavigationSplitView fail to update on some state changes. (91311311) Workaround: Wrap the contents of the column in a ZStack." The behaviour on macOS seems to be the same with or without, so I've removed it from the sample code in my question. – thiezn Jul 19 '22 at 15:09
  • I know about conditional but you don't have condition there... it just might affect something... everything actually have effect, so should be meaningful. And yes, I did not install macOS 13 yet, but with that ZStack there was issue on iPad as well. – Asperi Jul 19 '22 at 15:10
  • The app I'm trying to get this working in does have conditional views, but for the sake of this example I've removed the ZStack now. Somehow it's behaving differently on macOS.. – thiezn Jul 19 '22 at 15:13
0

Apple just releases macos13 beta 5 and they claimed this was resolved through feedback assistant, but unfortunately this doesn't seem to be the case.

I cross-posted this question on the apple developers forum and user nkalvi posted a workaround for this issue. I’ll post his example code here for future reference.


import SwiftUI

// Sample model definitions used to trigger navigation with navigationDestination API.
struct SampleModel1: Hashable, Identifiable {
    let id = UUID()
    
    static let samples = [SampleModel1(), SampleModel1(), SampleModel1()]
}

struct SampleModel2: Hashable, Identifiable {
    let id = UUID()
    
    static let samples = [SampleModel2(), SampleModel2(), SampleModel2()]
}


// The initial view loaded by the app. This will initialize the NavigationSplitView
struct ContentView: View {
    @State var path = NavigationPath()
    
    enum NavItem: Hashable, Equatable {
        case first
    }
    
    var body: some View {
        NavigationSplitView {
            List {
                NavigationLink(value: NavItem.first) {
                    Label("First", systemImage: "house")
                }
            }
        } detail: {
            SampleListView(path: $path)
        }

    }
}

// A list of SampleModel1 instances wrapped in a NavigationStack with multiple navigationDestinations
struct SampleListView: View {
    // Get the selection from DetailView and append to path
    // via .onChange
    @State var selection2: SampleModel2? = nil
    @Binding var path: NavigationPath
    
    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                Text("Path: \(path.count)")
                    .padding()
                List(SampleModel1.samples) { model in
                    NavigationLink("Model1: \(model.id)", value: model)
                }
                .navigationDestination(for: SampleModel2.self) { model in
                    Text("Model 2 ID \(model.id)")
                        .navigationTitle("navigationDestination(for: SampleModel2.self)")
                }
                .navigationDestination(for: SampleModel1.self) { model in
                    SampleDetailView(model: model, path: $path, selection2: $selection2)
                        .navigationTitle("navigationDestination(for: SampleModel1.self)")
                }
                .navigationTitle("First")
            }
            .onChange(of: selection2) { newValue in
                path.append(newValue!)
            }
        }
    }
}

// A detailed view of a single SampleModel1 instance. This includes a list
// of SampleModel2 instances that we would like to be able to navigate to
struct SampleDetailView: View {
    
    var model: SampleModel1
    @Binding var path: NavigationPath
    @Binding var selection2: SampleModel2?
    
    var body: some View {
        NavigationStack {
            Text("Path: \(path.count)")
                .padding()
            List(SampleModel2.samples, selection: $selection2) { model2 in
                NavigationLink("Model2: \(model2.id)", value: model2)
                // This also works (without .onChange):
                //                Button(model2.id.uuidString) {
                //                    path.append(model2)
                //                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
thiezn
  • 1,874
  • 1
  • 17
  • 24
  • Is this still working for you? When I click on a link in the detail view the app freezes and CPU goes up to 100 percent. If I read the SwiftUI instruments correctly the NavigationStack is constantly redrawn. – Dominik Dec 02 '22 at 09:38
  • @Dominik I haven't tested this workaround but Apple just released a proper fix in macos 13.3 beta. I recommend using that as this workaround is not really elegant and not sure if it works fully (anymore) – thiezn Feb 23 '23 at 15:33
0

Apple came back to be today mentioning that the issue should be resolved in macOS 13.3 Beta.

I've tested it and can confirm the issue no longer appears!! Navigation on macOS and iPad os works properly now, yay!

thiezn
  • 1,874
  • 1
  • 17
  • 24