Confusing SwiftUI error log: "Mutating observable property after view is torn down has no effect"

Hey,

I have a setup in my app that I am currently building, that uses @Observable router objects that hold the app's entire navigation state, so that I can easily set it globally and let SwiftUI show the appropriate views accordingly. Each view gets passed in such a router object and there is a global "app" router that the app's root view can access as an entry point:

@Observable @MainActor
final class AppRouter {
  static let shared = AppRouter() // Entry point used by the app's root view


  var isShowingSheet = false // Navigation state
  let sheetRouter = SheetRouter() // Router that's passed to the sheet view. This router could contain other routers for sheets it will show, and so on
}

@Observable @MainActor
final class SheetRouter { // Example of a "nested" router for a sheet view
  var path = NavigationPath()
  var isShowingOtherSheet = false

  func reset() {
    path = .init()
    isShowingOtherSheet = false
  }
}

To open a sheet, I have a button like this:

@Bindable var appRouter = AppRouter.shared
// ...
Button("Present") {
  appRouter.sheetRouter.reset() // Reset sheet router
  appRouter.isShowingSheet = true // show sheet
}

This seems to work perfectly fine. However, this produces tons of "error" logs in the console, whenever I open the sheet for a second time:

Mutating observable property \SheetRouter.path after view is torn down has no effect.

Mutating observable property \SheetRouter.path after view is torn down has no effect.

Mutating observable property \SheetRouter.path after view is torn down has no effect.

Mutating observable property \SheetRouter.path after view is torn down has no effect.

Mutating observable property \SheetRouter.isShowingOtherSheet after view is torn down has no effect.

These errors appear when calling the reset() of the sheet view's router before opening the sheet. That method simply resets all navigation states in a router back to their defaults. It's as if the sheetRouter is still connected to the dismissed view from the previous sheet, causing a mutation to trigger these error logs.

Am I misusing SwiftUI here or is that a bug? It's also worth mentioning that these error logs do not appear on iOS 17. Only on iOS 18. So it might be a bug but I just want to make sure my usage of these router objects is okay and not a misuse of the SwiftUI API that the runtime previously simply did not catch/notice. Then I'd have to rewrite my entire navigation logic.

I do have an example project to demonstrate the issue. You can find it here: https://github.com/SwiftedMind/PresentationBugDemo.

I have also filed a feedback for this: FB14162780

STEPS TO REPRODUCE

  1. Open the example project.
  2. Open the sheet in the ContentView twice by tapping "Present"
  3. Close that sheet
  4. Open it again.

Then the console will show these error logs from above.

I'd appreciate any help with this.

Cheers

Answered by Quantm in 794549022

The errors are gone in Xcode 16 Beta 3, so I also assume it was a bug :)

I believe this is a bug in iOS 18 or Xcode beta. I am seeing it a lot too in an app that I was testing in the new beta. Hadn't noticed the error before and I can see nothing wrong in the way I'm using views, SwiftData query objects etc.

Accepted Answer

The errors are gone in Xcode 16 Beta 3, so I also assume it was a bug :)

Confusing SwiftUI error log: "Mutating observable property after view is torn down has no effect"
 
 
Q