How to Dismiss the iOS Keyboard by Tapping Anywhere in Swift

How to Dismiss the iOS Keyboard by Tapping Anywhere in Swift

Written by

in

Quick answer: Add a UITapGestureRecognizer to the view controller’s view and call view.endEditing(true) in its action. Set tap.cancelsTouchesInView = false so buttons and table cells underneath still receive their taps. In SwiftUI, tap the background and clear a @FocusState value instead.

Why this happens

UIKit gives first responder status to exactly one view at a time. When a UITextField or UITextView becomes first responder, the system raises the keyboard and keeps it up until that view resigns. Nothing in UIKit dismisses the keyboard automatically when the user taps an empty area of the screen — the tap lands on the view controller’s root view, which has no idea a text field is active.

So the keyboard stays. On a short form it covers the fields below, and the user has to hunt for a Return key or a Done button that may not even exist for a multi-line UITextView.

The fix is to make the root view notice the tap and then tell the whole view hierarchy to stop editing. UIView.endEditing(_:) does that in one call: it asks the current first responder — or any embedded text field — to resign, and the keyboard slides away.

How to fix it

1. Add a tap gesture that calls endEditing

This is the shortest working solution. Create a UITapGestureRecognizer in viewDidLoad, point it at a method that ends editing, and attach it to the controller’s view.

import UIKit

class ItemViewController: UIViewController {

    @IBOutlet weak var nameField: UITextField!
    @IBOutlet weak var notesField: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let tap = UITapGestureRecognizer(target: self,
                                         action: #selector(dismissKeyboard))
        view.addGestureRecognizer(tap)
    }

    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
}

Two details matter here:

  • The action method must be marked @objc, because #selector needs an Objective-C entry point.
  • endEditing(true) forces the resignation even if the field is mid-edit. Passing false asks the delegate first and can be refused.

2. Stop the gesture from swallowing other taps

By default a gesture recognizer sets cancelsTouchesInView = true. On a screen with buttons, table view cells or collection view items, the tap gesture can win the race and the underlying control never sees the touch. That shows up as “my buttons stopped working after I added the keyboard dismissal”.

Set the property to false before adding the recognizer:

let tap = UITapGestureRecognizer(target: self,
                                 action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)

Now the tap both dismisses the keyboard and reaches the control underneath, so a button press still fires its action.

3. Make it reusable with a UIViewController extension

Copying the same six lines into every screen gets old fast. Put the behaviour in an extension once and call it from each controller that needs it.

import UIKit

extension UIViewController {

    func hideKeyboardWhenTappedAround() {
        let tap = UITapGestureRecognizer(target: self,
                                         action: #selector(UIViewController.dismissKeyboard))
        tap.cancelsTouchesInView = false
        view.addGestureRecognizer(tap)
    }

    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
}

Usage becomes a single line:

override func viewDidLoad() {
    super.viewDidLoad()
    hideKeyboardWhenTappedAround()
}

Because the selector targets UIViewController.dismissKeyboard rather than self, subclasses that override the method don’t break the recognizer’s target-action pairing.

If you only want the keyboard gone when the user taps outside a text field, keep the gesture but check the touch location, or simply rely on cancelsTouchesInView = false and let the text field handle its own taps normally.

4. Use touchesBegan instead of a gesture recognizer

If you would rather not add a recognizer at all, override the touch handler on the view controller. This fires for taps that reach the controller’s view and nothing else.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    view.endEditing(true)
    super.touchesBegan(touches, with: event)
}

This is fine for simple screens, but it does not fire when the tap is consumed by a subview such as a UITableView or a UIScrollView, so the gesture approach is more reliable on real screens.

5. Also handle the Return key

Tapping the background is a convenience, not a replacement for the keyboard’s own dismissal path. Conform to UITextFieldDelegate and resign on Return as well:

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

Remember to set delegate = self on each field, otherwise the method is never called.

6. The SwiftUI equivalent

SwiftUI has no endEditing. Track focus with @FocusState and clear it on a background tap.

import SwiftUI

struct NoteEditor: View {
    @State private var text = ""
    @FocusState private var isFocused: Bool

    var body: some View {
        ZStack {
            Color.clear
                .contentShape(Rectangle())
                .onTapGesture { isFocused = false }

            TextField("Note", text: $text)
                .focused($isFocused)
                .textFieldStyle(.roundedBorder)
                .padding()
        }
    }
}

The contentShape(Rectangle()) is what makes the transparent area tappable; without it, the empty space has no hit-testable shape. For forms inside a ScrollView, .scrollDismissesKeyboard(.interactively) gives the drag-to-dismiss behaviour users expect on iOS 16 and later.

How to prevent it

  • Set cancelsTouchesInView = false on any dismissal gesture you add to a screen with buttons, cells or links.
  • Add the gesture once, in viewDidLoad, not in viewWillAppear — repeated calls stack recognizers and fire the action multiple times.
  • Prefer view.endEditing(true) over calling resignFirstResponder() on each field individually; it covers text views and any field you add later.
  • Keep the Return-key delegate path as well, so hardware keyboards and external input still work.
  • In SwiftUI, always pair .onTapGesture on a background with .contentShape(Rectangle()), or the tap will only register on drawn pixels.

FAQ

Does view.endEditing(true) dismiss the keyboard for a UITextView too?

Yes. endEditing(_:) walks the view hierarchy and resigns whichever descendant is currently first responder, so it works for UITextField, UITextView and any custom view that adopts UIResponder.

Why did my table view stop responding after adding the tap gesture?

Because the recognizer’s cancelsTouchesInView defaults to true, so it cancels the touch before the cell sees it. Set it to false when you create the recognizer and cell selection works again.

Can I dismiss the keyboard only when tapping outside a text field?

Yes. Implement gestureRecognizer(_:shouldReceive:), return false when touch.view is a UITextField or UITextView, and true otherwise. That keeps taps on the field itself from triggering the dismissal.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.