I am struggling with a bug and I just can't seem to solve it, or where to look further.
The problem occurs when I try to remove a view (which holds a NavigationView) from the view hierarchy. It crashes with: Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
After experimenting with the sanitizer I got this output in the debugger: *** -[_TtGC7SwiftUI41StyleContextSplitViewNavigationControllerVS_19SidebarStyleContext_ removeChildViewController:]: message sent to deallocated instance 0x10904c880
Which pointed me to figure out that it was the NavigationView that cause it somehow. But I still can't figure out how to get from here.
This problem ONLY occurs on a real device, it works just fine in a simulator and you may have to hit the login, and then log out and log back in a few times before the crash happens.
I made a sample app with the example: https://github.com/Surferdude667/NavigationRemoveTest
The code is as follows:
NavigationRemoveTestApp
@main
struct NavigationRemoveTestApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
}
}
RootView
struct RootView: View {
@StateObject private var viewModel = RootViewModel()
var body: some View {
if !viewModel.loggedIn {
WelcomeView()
} else {
ContentView()
}
}
}
RootViewModel
class RootViewModel: ObservableObject {
@Published var loggedIn = false
init() {
LogInController.shared.loggedIn
.receive(on: DispatchQueue.main)
.assign(to: &$loggedIn)
}
}
WelcomeView
struct WelcomeView: View {
var body: some View {
NavigationView {
VStack {
Text("Welcome")
NavigationLink("Go to login") {
LogInView()
}
}
}
}
}
LogInView
struct LogInView: View {
var body: some View {
VStack {
Text("Log in view")
Button("Log in") {
LogInController.shared.logIn()
}
}
}
}
ContentView
struct ContentView: View {
var body: some View {
VStack {
Text("Content view")
Button("Log out") {
LogInController.shared.logOut()
}
}
}
}
LogInController
import Combine
class LogInController {
static let shared = LogInController()
var loggedIn: CurrentValueSubject<Bool, Never>
private init() {
self.loggedIn = CurrentValueSubject<Bool, Never>(false)
}
func logIn() {
self.loggedIn.send(true)
}
func logOut() {
self.loggedIn.send(false)
}
}
I found a few solutions.
Either you wrap the if statement in the RootView
with a NavigationView
instead of having the NavigationView
inside the actual views, it works. This is however not very convenient since everything is now wrapped in a NavigationView.
Replacing NavigationView with the new iOS 16 NavigationStack
also solves it.