109

How can I detect when the keyboard is shown and hidden from my application?

Zoe Edwards
  • 12,999
  • 3
  • 24
  • 43
user198725878
  • 6,266
  • 18
  • 77
  • 135

14 Answers14

178

In the ViewDidLoad method of your class set up to listen for messages about the keyboard:

// Listen for keyboard appearances and disappearances
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(keyboardDidShow:)
                                             name:UIKeyboardDidShowNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardDidHide:)
                                             name:UIKeyboardDidHideNotification
                                           object:nil];

Then in the methods you specify (in this case keyboardDidShow and keyboardDidHide) you can do something about it:

- (void)keyboardDidShow: (NSNotification *) notif{
    // Do something here
}

- (void)keyboardDidHide: (NSNotification *) notif{
    // Do something here
}
Matthew Frederick
  • 22,245
  • 10
  • 71
  • 97
  • Does not work if you tab thru fields. Wonder what the solution for that would be and if you can even tab thru on an actual iPad? – i-- Oct 10 '12 at 04:22
  • @apprentice Do you mean that the keyboard doesn't show if you tab? – Matthew Frederick Oct 10 '12 at 05:20
  • if there are fields still covered by keyboard below the one with the focus, the view will stay still on tab due to notification being dispatched only at the moment the keyboard slides up – i-- Oct 10 '12 at 12:52
  • 3
    @apprentice You have to manage that by hand, sliding the scrollview around based on each text field becoming active, a different problem than knowing when the keyboard appears. Make your view controller a `UITextFieldDelegat`e, then implement the `textFieldShouldReturn:` method. You'll get the `textField` just entered as an argument, which you can compare to your own textFields and scroll the `scrollView` so that the appropriate textField is showing. – Matthew Frederick Oct 10 '12 at 21:51
110

You may just need addObserver in viewDidLoad. But having addObserver in viewWillAppear and removeObserver in viewWillDisappear prevents rare crashes which happens when you are changing your view.

Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear), name: UIResponder.keyboardWillHideNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear), name: UIResponder.keyboardWillShowNotification, object: nil)
}

@objc func keyboardWillAppear() {
    //Do something here
}

@objc func keyboardWillDisappear() {
    //Do something here
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

Swift 3 and 4

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear), name: Notification.Name.UIKeyboardWillHide, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear), name: Notification.Name.UIKeyboardWillShow, object: nil)
}

@objc func keyboardWillAppear() {
    //Do something here
}

@objc func keyboardWillDisappear() {
    //Do something here
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

Older Swift

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyboardWillAppear:", name: UIKeyboardWillShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyboardWillDisappear:", name: UIKeyboardWillHideNotification, object: nil)
}

func keyboardWillAppear(notification: NSNotification){
    // Do something here
}

func keyboardWillDisappear(notification: NSNotification){
    // Do something here
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    NSNotificationCenter.defaultCenter().removeObserver(self)
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
  • 9
    If you remove your observer on viewWillDisappear... you should add it into the viewWillAppear instead of viewDidLoad. – FouZ Jul 12 '15 at 20:24
  • That is true, feel free to edit the answer. I will accept it – Esqarrouth Jul 12 '15 at 21:10
  • @FouZ is it better to remove the observers from `deinit` like this: `deinit { NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil) }` – Crashalot Apr 07 '16 at 22:44
  • In Swift 3, the above deinit code block is like: `deinit { NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil) }` – Md. Najmul Hasan May 03 '17 at 06:28
  • @Crashalot the deinit doesn't run until you dismiss the vc. so if you present another vc on top of this one it will still receive the notifications. I believe the purpose is to only listen to this notifications while this vc is visible so adding it on viewdidappear and removing it on viewdiddissapear seems better to me. – Pochi Jul 12 '17 at 02:01
  • @FouZ hi, I wonder the reason of why you should add it into the viewWillAppear – eemrah Feb 21 '18 at 12:37
  • @FouZ I've aligned the snippets logic for the different Swift versions, so the balance with `viewWillDisappear` is now resolved. – Cœur Oct 24 '18 at 04:49
20

Swift 3:

NotificationCenter.default.addObserver(self, selector: #selector(viewController.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(viewController.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

func keyboardWillShow(_ notification: NSNotification){
    // Do something here
}

func keyboardWillHide(_ notification: NSNotification){
    // Do something here
}
dichen
  • 1,643
  • 14
  • 19
14

Swift 5

There answers above are correct. Although I would prefer to create a helper to wrap up the notification's observers.

The benefit:

  1. You don't have to repeat each time you handle the keyboard behaviors.
  2. You can extend other notification by implement other enum value
  3. It's useful when you have to deal with keyboard in several controllers.

Sample code:

extension KeyboardHelper {
    enum Animation {
        case keyboardWillShow
        case keyboardWillHide
    }

    typealias HandleBlock = (_ animation: Animation, _ keyboardFrame: CGRect, _ duration: TimeInterval) -> Void
}

final class KeyboardHelper {
    private let handleBlock: HandleBlock

    init(handleBlock: @escaping HandleBlock) {
        self.handleBlock = handleBlock
        setupNotification()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    private func setupNotification() {
        _ = NotificationCenter.default
            .addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { [weak self] notification in
                self?.handle(animation: .keyboardWillShow, notification: notification)
            }

        _ = NotificationCenter.default
            .addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { [weak self] notification in
                self?.handle(animation: .keyboardWillHide, notification: notification)
            }
    }

    private func handle(animation: Animation, notification: Notification) {
        guard let userInfo = notification.userInfo,
            let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
            let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
        else { return }

        handleBlock(animation, keyboardFrame, duration)
    }
}

How to use:

private var keyboardHelper: KeyboardHelper?
...

override func viewDidLoad() {
   ...
   keyboardHelper = KeyboardHelper { [unowned self] animation, keyboardFrame, duration in
        switch animation {
        case .keyboardWillShow:
            print("keyboard will show")
        case .keyboardWillHide:
            print("keyboard will hide")
        }
    }

}

nahung89
  • 7,745
  • 3
  • 38
  • 40
  • hmm look not good to me, i want to detect keyboard event many time, i think better is create class to listen keyboard event and destroy immediately after received event – famfamfam Mar 30 '21 at 19:50
  • @famfamfam Not sure what you mean, but the implementation above is able to list the keyboard events continuously, until the `keyboardHelper` instance is released – nahung89 Apr 07 '21 at 16:17
10

Swift 4:

  NotificationCenter.default.addObserver( self, selector: #selector(ControllerClassName.keyboardWillShow(_:)),
  name: Notification.Name.UIKeyboardWillShow,
  object: nil)
  NotificationCenter.default.addObserver(self, selector: #selector(ControllerClassName.keyboardWillHide(_:)),
  name: Notification.Name.UIKeyboardWillHide,
  object: nil)

Next, adding method to stop listening for notifications when the object’s life ends:-

Then add the promised methods from above to the view controller:
deinit {
  NotificationCenter.default.removeObserver(self)
}
func adjustKeyboardShow(_ open: Bool, notification: Notification) {
  let userInfo = notification.userInfo ?? [:]
  let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
  let height = (keyboardFrame.height + 20) * (open ? 1 : -1)
  scrollView.contentInset.bottom += height
  scrollView.scrollIndicatorInsets.bottom += height
}

@objc func keyboardWillShow(_ notification: Notification) {
  adjustKeyboardShow(true, notification: notification)
}
@objc func keyboardWillHide(_ notification: Notification) {
  adjustKeyboardShow(false, notification: notification)
}
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
  • The `+=` appears to make the insets get larger and larger. – Wez Jul 13 '17 at 16:02
  • I think the adjustKeyboardShow function is a very well-made function. Thank you. – hong developer Nov 08 '19 at 07:45
  • as of Swift 5 notification name is `UIResponder.keyboardWillShowNotification` and `UIResponder.keyboardWillHideNotification`, and the keyboard info key is `UIResponder.keyboardFrameBeginUserInfoKey`. – CodeBrew Jan 20 '20 at 22:16
6

In Swift 4.2 the notification names have moved to a different namespace. So now it's

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    addKeyboardListeners()
}


override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}


func addKeyboardListeners() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}

@objc private extension WhateverTheClassNameIs {

    func keyboardWillShow(_ notification: Notification) {
        // Do something here.
    }

    func keyboardWillHide(_ notification: Notification) {
        // Do something here.
    }
}
Community
  • 1
  • 1
Sebbo
  • 326
  • 4
  • 10
5

You'll want to register yourself for the 2 keyboard notifications:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name: UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (keyboardDidHide:) name: UIKeyboardDidHideNotification object:nil];

Great post on how to adjust your TextField to the keyboard - http://iosdevelopertips.com/user-interface/adjust-textfield-hidden-by-keyboard.html

Cœur
  • 37,241
  • 25
  • 195
  • 267
ChrisInTX
  • 51
  • 2
5

Swift - 4

override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated)
   addKeyBoardListener()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self) //remove observer
}

func addKeyBoardListener() {
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil);
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil);
}

@objc func keyboardWillShow(_ notification: Notification) {

}

@objc func keyboardWillHide(_ notification: Notification) {

}
Sebbo
  • 326
  • 4
  • 10
Rahul
  • 337
  • 3
  • 13
4

Check out the Managing the Keyboard section of the "Text, Web, and Editing Programming Guide" for information on tracking the keyboard being shown or hidden, and how to display/dismiss it manually.

Justin Spahr-Summers
  • 16,893
  • 2
  • 61
  • 79
2

Swift 4 - dd 20 october 2017

override func viewDidLoad() {
    [..]

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
}

@objc func keyboardWillAppear(_ notification: NSNotification) {
    if let userInfo = notification.userInfo, 
       let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue).cgRectValue {
           let inset = keyboardFrame.height // if scrollView is not aligned to bottom of screen, subtract offset
           scrollView.contentInset.bottom = inset
           scrollView.scrollIndicatorInsets.bottom = inset
    }
}

@objc func keyboardWillDisappear(_ notification: NSNotification) {
    scrollView.contentInset.bottom = 0
    scrollView.scrollIndicatorInsets.bottom = 0
}

deinit {
    NotificationCenter.default.removeObserver(self)
}
dOM
  • 555
  • 5
  • 14
1

If you have more then one UITextFields and you need to do something when (or before) keyboard appears or disappears, you can implement this approach.

Add UITextFieldDelegate to your class. Assign integer counter, let's say:

NSInteger editCounter; 

Set this counter to zero somewhere in viewDidLoad. Then, implement textFieldShouldBeginEditing and textFieldShouldEndEditing delegate methods.

In the first one add 1 to editCounter. If value of editCounter becomes 1 - this means that keyboard will appear (in case if you return YES). If editCounter > 1 - this means that keyboard is already visible and another UITextField holds the focus.

In textFieldShouldEndEditing subtract 1 from editCounter. If you get zero - keyboard will be dismissed, otherwise it will remain on the screen.

Cee McSharpface
  • 8,493
  • 3
  • 36
  • 77
user2248258
  • 161
  • 2
  • 5
0

You could use KBKeyboardObserver library. It contains some examples and provides simple interface.

kam800
  • 547
  • 5
  • 9
0

There is a CocoaPods to facilitate the observation on NSNotificationCentr for the keyboard's visibility here: https://github.com/levantAJ/Keyhi

pod 'Keyhi'

Tai Le
  • 8,530
  • 5
  • 41
  • 34
-1

So ah, this is the real answer now.

import Combine


class MrEnvironmentObject {
    /// Bind into yr SwiftUI views
    @Published public var isKeyboardShowing: Bool = false

    /// Keep 'em from deallocatin'
    var subscribers: [AnyCancellable]? = nil

    /// Adds certain Combine subscribers that will handle updating the
    ///  `isKeyboardShowing` property 
    ///
    /// - Parameter host: the UIHostingController of your views. 
    func setupSubscribers<V: View>(
        host: inout UIHostingController<V>
    ) {
        subscribers = [
            NotificationCenter
                .default
                .publisher(for: UIResponder.keyboardWillShowNotification)
                .sink { [weak self] _ in
                    self?.isKeyboardShowing = true
                },
            NotificationCenter
                .default
                .publisher(for: UIResponder.keyboardWillHideNotification)
                .sink { [weak self, weak host] _ in
                    self?.isKeyboardShowing = false
                    // Hidden gem, ask me how I know:
                    UIAccessibility.post(
                        notification: .layoutChanged, 
                        argument: host
                    )
                },
            // ...
            Profit
                .sink { [weak self] profit in profit() },
        ]
    }
}
scaly
  • 509
  • 8
  • 18