The Problem
My team has recently discovered a problem in one of our macOS apps. It uses SwiftUI and Combine, as it still needs to support macOS 13.
It uses a NavigationSplitView to display a sidebar-detail UI.
On macOS 15, we started running into a bug: When selecting an item from the sidebar, the corresponding detail does not initially show up, and a detail page shows up only when selecting a second item from the sidebar. The following message would be printed into Xcode's console:
Publishing changes from within view updates is not allowed, this will cause undefined behavior.
This message would only get printed into the console, and would not be attached to any specific line.
Oftentimes, selecting a subsequent item from the sidebar also either completely clears the detail page, doesn't do anything, and there was a crash reported as well.
The app is compiled using Xcode 16.0.
Architecture Overview
The app uses NavigationSplitView
with multiple different detail pages. .id
is attached to the detail view in its corresponding NavigationLink
, which used to properly show the corresponding detail page.
The ID is stored in an observable AppState
, which uses Combine's ObservableObject
in the production app, and Observation's Observable
in the minimal example.
Example Code
Minimal Reproductible Example
You can see a minimal reproducible example here: https://github.com/buresdv/Navigation-Problems
This example also features a debug field at the end of the sidebar, called "navigationSelection". When clicking one of the items, the ID gets correctly set, but the detail page does not update.
Production App
The production app is a bit more complex, but the core system is the same.
For complete code, see: https://github.com/buresdv/Cork
Some relevant lines include the navigation root, sidebar list, and the navigation links
Notes
- We were able to reproduce these issues on multiple user systems
- This issue seems to have started occuring when we updated to macOS 15
- At the same time, we updated the app to Strict Concurrency Checking and Swift 6, although we did not change anything in the navigation system
This was release noted here: https://developer.apple.com/documentation/macos-release-notes/macos-15-release-notes (Search 127626852)
Behavior was changed because the composition is undefined on iOS and was undefined on macOS, but happened to work in some cases.
This change was made behind a link check, so hopefully you are only seeing this when compiling against the newest Sequoia toolchains.
View-destination links (e.g. NavigationLink
s that take a View
as their destination) and value-destination links (e.g. NavigationLink
s that take a value argument) cannot be mixed.
By attaching an id
to the link, you are wiring the link up to the List
s selection, and thus trying to drive navigation that way. The correct way to do this in releases including and prior to iOS 15 and macOS 18 is by switching over the selection in the NavigationSplitView
's subsequent column:
NavigationSplitView {
List(selection: $selection) {
NavigationLink("Value-destination link", value: 5)
}
} detail: {
if let selection {
SelectedDestination(selection)
} else {
ContentUnavailableView(...)
}