Search code examples
swiftuiswiftui-navigationlinkios16swiftui-navigationsplitviewipados16

SwiftUI: How to update detail column from a distant child using iOS16/iPadOS16 NavigationSplitView and NavigationLink


I'm trying to update an older app to use the new NavigationSplitView and NavigationLink, but trying to wrap my head around the proper way to do it when the sidebar has a hierarchy of child objects and I want to update the detail view from a distant child object. For example, based on the WWDC2022 example project Navigation Cookbook I tried the following and it didn't work:

TestApp.swift

@main

struct TestApp: App {
  @StateObject var appState = AppState()

  var body: some Scene {
    WindowGroup {

      NavigationSplitView {
         ProjectListView()
      } detail: {
        if let chapter = appState.chapter {
          ChapterDetailView(chapter: chapter)
        } else {
          Text("Pick a Chapter")
        }
      }
    }
  }
}

ChapterListView.swift < A distant (3 levels down) sibling of ProjectListView()

List(selection: $appState.chapter) {
  ForEach(chapters) { chapter in
    NavigationLink(chapter.title ?? "A Chapter", value: chapter)
  }
}

appState.swift

class AppState: ObservableObject {
  @Published var chapter: Chapter?
}

I'm sure I'm just not understanding the basics of how the new way of doing navigation works. Yes, I am targeting iOS16


Solution

  • This is a known bug in beta 1. To workaround, the logic in the detail section needs to be wrapped in a ZStack. From the release notes:

    Conditional views in columns of NavigationSplitView fail to update on some state changes. (91311311) Workaround: Wrap the contents of the column in a ZStack. TestApp works if changed to this:

    @main
    
    struct TestApp: App {
      @StateObject var appState = AppState()
    
      var body: some Scene {
        WindowGroup {
    
          NavigationSplitView {
             ProjectListView()
          } detail: {
    // Wrap in a ZStack to fix this bug
           ZStack {
            if let chapter = appState.chapter {
              ChapterDetailView(chapter: chapter)
            } else {
              Text("Pick a Chapter")
            }
           }
          }
        }
      }
    }