From 4f83ddb4c28fa0fe1348d18621c431cba6588a16 Mon Sep 17 00:00:00 2001 From: Fanboynz Date: Wed, 23 Sep 2026 14:43:46 +1200 Subject: [PATCH] ui: take the keyboard watcher's recognizer off the window in deinit too The tap recognizer that decides whether a tap landed in a text field lives on the WINDOW, which outlives the view that added it, so it has to be removed by hand. dismantleUIView and didMoveToWindow cover the teardowns SwiftUI tells us about; nothing covered the ones it does not. A left-behind recognizer is quiet rather than harmful, since UIGestureRecognizer holds its target weakly and nothing fires. But one accumulates per appearance, and once the Probe is gone its delegate is nil, so the filter that makes the recognizer safe (ignore touches that land in a text input, ignore touches outside this screen's controller) is no longer applied to it. From reviewing #2403, which added the watcher. --- Strand/Screens/KeyboardDismiss.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Strand/Screens/KeyboardDismiss.swift b/Strand/Screens/KeyboardDismiss.swift index 9b5d6b8282..7bee16e7cc 100644 --- a/Strand/Screens/KeyboardDismiss.swift +++ b/Strand/Screens/KeyboardDismiss.swift @@ -90,6 +90,14 @@ private struct TapOutsideFields: UIViewRepresentable { recognizer = nil } + /// The recognizer lives on the WINDOW, which outlives this view, so it has to be taken off by + /// hand. `dismantleUIView` and `didMoveToWindow` cover the teardowns SwiftUI tells us about; + /// this covers the ones it does not. A recognizer left behind is quiet rather than harmful + /// (`UIGestureRecognizer` holds its target weakly, so nothing fires) but it accumulates one per + /// appearance, and with the Probe gone its delegate is nil too, so the "ignore text inputs" + /// filter that makes it safe is no longer being applied. + deinit { detach() } + @objc private func tapped() { onTap() } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,