Memory Leak in WatchOS 11.2 with NavigationTitle in Presented Sheet

I would like to report a memory leak issue in watchOS 11.2 that occurs when using .navigationTitle() inside a sheet. This behavior is reproducible both on the simulator and on a real device, but not on iOS. While this does not register as a leak in Instruments, the deinit of the DetailsViewModel is never called, and multiple instances of the view model accumulate in the Memory Graph Debugger. Commenting out .navigationTitle("Sheet View") resolves the issue, and deinit prints as expected. Using @MainActor on the DetailsViewModel does not fix the issue. Nor does switching to @StateObject and using ObservableObject resolve the memory retention.

This issue seems related to other SwiftUI memory leaks that have been reported:

https://developer.apple.com/forums/thread/738840

https://developer.apple.com/forums/thread/736110?login=true&page=1#769898022

https://developer.apple.com/forums/thread/737967?answerId=767599022#767599022

Feedback Number: FB16442048

struct MainView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("Details", value: 1)
                .navigationDestination(for: Int.self) { _ in
                    DetailsView()
                }
        }
    }
}

struct SheetObject: Identifiable {
    let id = UUID()
    let date: Date
    let value: Int
}

@Observable
@MainActor
final class DetailsViewModel {
    var sheetObject: SheetObject?
    
    init() {
        print("Init")
    }
    
    deinit {
        print("Deinit")
    }
    
    func onAppear() async {
        try? await Task.sleep(for: .seconds(2))
        sheetObject = .init(date: .now, value: 1)
    }
}

struct DetailsView: View {
    @State private var viewModel = DetailsViewModel()
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        Text("Detail View. Going to sheet, please wait...")
            .task {
                await viewModel.onAppear()
            }
            .sheet(item: $viewModel.sheetObject) { sheetObject in
                SheetView(sheetObject: sheetObject)
                    .onDisappear {
                        dismiss()
                    }
            }
    }
}

struct SheetView: View {
    let sheetObject: SheetObject
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        NavigationStack {
            VStack {
                Text(sheetObject.date.formatted())
                Text(sheetObject.value.formatted())
                Button("Dismiss") {
                    dismiss()
                }
            }
            .navigationTitle("Sheet View") // This line causes a memory leak. Commenting out, you will see "Deinit" be printed.
        }
    }
}

Thanks for the post and apologies for the delay.

Do you get the same results with just the relevant code in a small test project? If so, please share a link to your test project. That'll help us better understand what's going on. If you're not familiar with preparing a test project, take a look at Creating a test project.

Albert Pascual
  Worldwide Developer Relations.

This result does happen with a small sample project. I can't upload the zip file here, but it is attached to the original Feedback (FB16442048).

Memory Leak in WatchOS 11.2 with NavigationTitle in Presented Sheet
 
 
Q