I am seeing a SwiftUI environment object remain alive after the view hierarchy that owns it has been dismissed.
The retained object is owned by @StateObject inside a fullScreenCover. A TextField presented deeper in that hierarchy receives focus through @FocusState.
After navigating back and dismissing the cover, the @StateObject does not deinitialize. The memory graph shows that system keyboard/input-assistant views retain a UITraitCollection, which retains a SwiftUIEnvironmentWrapper containing the environment object.
Environment
- macOS 26.6.1 (25G76)
- Xcode 26.6 (17F113)
- iOS 26.5 Simulator
- iPhone 11 26.6
Minimal reproduction
import Combine
import SwiftUI
final class DemoCoordinator: ObservableObject {
let id = UUID()
init() {
print("[+] DemoCoordinator INIT \(id)")
}
deinit {
print("[-] DemoCoordinator DEINIT \(id)")
}
}
@main
struct FocusStateLeakApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
}
}
struct RootView: View {
@State private var isFlowPresented = false
var body: some View {
Button("Present flow") {
isFlowPresented = true
}
.fullScreenCover(isPresented: $isFlowPresented) {
FlowContainer()
}
}
}
struct FlowContainer: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var coordinator = DemoCoordinator()
var body: some View {
NavigationStack {
NavigationLink("Push field screen") {
FieldScreen()
}
.navigationTitle("Flow")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Close") {
dismiss()
}
}
}
}
.environmentObject(coordinator)
}
}
struct FieldScreen: View {
@State private var text = ""
@FocusState private var isFocused: Bool
var body: some View {
TextField("Type something", text: $text)
.textFieldStyle(.roundedBorder)
.focused($isFocused)
.padding()
.navigationTitle("Field")
.onAppear {
isFocused = true
}
.onDisappear {
isFocused = false
}
}
}
Steps to reproduce
- Launch the application.
- Tap “Present flow”.
- Tap “Push field screen”.
- The TextField automatically becomes focused and the software keyboard appears.
- Navigate back.
- Tap “Close” to dismiss the full-screen cover.
- Observe the console or capture a memory graph.
Expected result:
After the full-screen cover is dismissed, FlowContainer and its @StateObject storage should be released. The following message should be printed:
[-] DemoCoordinator DEINIT ...
Actual result:
DemoCoordinator remains alive and its deinit is not called. The captured memory graph contains one DemoCoordinator.
The same behavior occurs after migrating the reproducer to the Observation framework.
Questions
- Is retaining the SwiftUI environment through keyboard-owned cached trait collections expected?
- Is there a supported way to prevent dismissed SwiftUI environments from being retained by the input-assistant hierarchy?
- Is this a known UIKit/SwiftUI issue for which a Feedback ID already exists?
- Should this be reported as a SwiftUI issue, UIKit issue, or Text Input issue in Feedback Assistant?
I can provide the minimal Xcode project and the captured .memgraph file if needed.