This thread has an answer that directly addresses your exact question in a clean way that actually works without interfering with any other controls.
If you combine the top answer with info found in the comments of that answer and by using code provided by Mikhail in his answer on the same thread, you can do it completely in SwiftUI without the need for an App Delegate or Scene Delegate.
Here is the code:
extension UIApplication {
func addTapGestureRecognizer() {
guard let window = (connectedScenes.first as? UIWindowScene)?.windows.first else { return }
let tapGesture = AnyGestureRecognizer(target: window, action: #selector(UIView.endEditing))
tapGesture.requiresExclusiveTouchType = false
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self
window.addGestureRecognizer(tapGesture)
}
}
extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // set to `false` if you don't want to detect tap during other gestures
}
}
class AnyGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if let touchedView = touches.first?.view, touchedView is UIControl {
state = .cancelled
} else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {
state = .cancelled
} else {
state = .began
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
state = .ended
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
state = .cancelled
}
}
Then call UIApplication.shared.addTapGestureRecognizer()
inside of an .onAppear()
block on your main root view.
I suggest you follow the links provided and read their answers and the comments to better understand the how and why.