15

How do I prevent a UITextField from being hidden by the keyboard?

arcyqwerty
  • 10,325
  • 4
  • 47
  • 84
user3362882
  • 151
  • 1
  • 1
  • 6

13 Answers13

14

I assume this is happening on a UIViewController. If so, you can setup the following two functions to be called when the keyboard will show/hide, and respond appropriately in their blocks.

Setting up the UIViewController:

class ViewController: UIViewController, UITextFieldDelegate... {

    var frameView: UIView!

First, in viewDidLoad():

override func viewDidLoad() {

    self.frameView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))

    // Keyboard stuff.
    let center: NotificationCenter = NotificationCenter.default
    center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
    center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)

}

Then implement the following two functions to respond to your NotificationCenter functions defined in viewDidLoad() above. I give you an example of moving the entire view, but you can also animate just the UITextFields.

@objc func keyboardWillShow(notification: NSNotification) {
    let info:NSDictionary = notification.userInfo! as NSDictionary
    let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue

    let keyboardHeight: CGFloat = keyboardSize.height

    let _: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat


    UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
        self.frameView.frame = CGRect(x: 0, y: (self.frameView.frame.origin.y - keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
    }, completion: nil)
}

@objc func keyboardWillHide(notification: NSNotification) {
    let info: NSDictionary = notification.userInfo! as NSDictionary
    let keyboardSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue

    let keyboardHeight: CGFloat = keyboardSize.height

    let _: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat

    UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
        self.frameView.frame = CGRect(x: 0, y: (self.frameView.frame.origin.y + keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
    }, completion: nil)

}

Don't forget to remove the notifications when leaving your view

override func viewWillDisappear(_ animated: Bool) {
    NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
Theo
  • 3,826
  • 30
  • 59
Alex
  • 5,298
  • 4
  • 29
  • 34
  • What's the variable frameview? – user3362882 Sep 24 '14 at 03:23
  • Sorry, left that code out. frameview was simply a UIView that was the entire width and height of the UIViewController. Updated in my answer. But keep in mind you don't have to move the entire view up, you could just move the positions of the uitextview. – Alex Sep 24 '14 at 03:27
  • And how i move the position of the uitextview? – user3362882 Sep 24 '14 at 03:41
  • 1
    For example: You could position your UITextView in a UIView, say called textViewWrapperView - which would be positioned in your viewDidLoad function. Then in keyboardWillShow and keyboardWillHide instead of altering the position of self.frameView.frame you would alter the position of self.textViewWrapperView.frame. Make sense? – Alex Sep 24 '14 at 03:45
  • @Alex, I've updated this to satisfy the Swift 2.0 compiler. I wanted to notify you because I am changing your answer slightly and this will give you the opportunity to change my edit. – Dan Beaulieu Sep 29 '15 at 20:05
  • This does not work for me, the view temporarily moves down and leaves a big black space above it then slides back to its original position. – Duncan Groenewald Jan 20 '16 at 23:48
  • I prefer Dan's solution. Also when you have multiple UITextFields this will slide the View up multiple times. I prevented this with a "keyboardShowing" boolean. – Deddiekoel Aug 04 '16 at 07:19
  • For anyone using a `storyboard` and trying to make it work but can't, just make sure that `self.view` has **leading**, **top** and **trailing** `constraints`. Otherwise, it probably won't work. – Burak Aug 08 '16 at 14:53
  • Note for iOS13 iPad apps the UIKeyboardIsLocalUserInfoKey should be tested, if it is not 1 then the keyboard notification is being generated for another app. Refer: https://developer.apple.com/documentation/uikit/uikeyboardislocaluserinfokey?language=objc – ptc Dec 12 '19 at 00:56
9

Here is the simple solution in Swift. I translated some Objective-C code that worked for me in the past.

func textFieldDidBeginEditing(textField: UITextField) { // became first responder

    //move textfields up
    let myScreenRect: CGRect = UIScreen.mainScreen().bounds
    let keyboardHeight : CGFloat = 216

    UIView.beginAnimations( "animateView", context: nil)
    var movementDuration:NSTimeInterval = 0.35
    var needToMove: CGFloat = 0

    var frame : CGRect = self.view.frame
    if (textField.frame.origin.y + textField.frame.size.height + /*self.navigationController.navigationBar.frame.size.height + */UIApplication.sharedApplication().statusBarFrame.size.height > (myScreenRect.size.height - keyboardHeight)) {
    needToMove = (textField.frame.origin.y + textField.frame.size.height + /*self.navigationController.navigationBar.frame.size.height +*/ UIApplication.sharedApplication().statusBarFrame.size.height) - (myScreenRect.size.height - keyboardHeight);
    }

    frame.origin.y = -needToMove
    self.view.frame = frame
    UIView.commitAnimations()
}

func textFieldDidEndEditing(textField: UITextField) {
        //move textfields back down
        UIView.beginAnimations( "animateView", context: nil)
        var movementDuration:NSTimeInterval = 0.35
        var frame : CGRect = self.view.frame
        frame.origin.y = 0
        self.view.frame = frame
        UIView.commitAnimations()
}
Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
geiger zaehler
  • 119
  • 2
  • 4
5

Swift 4 code, It is very simple instead of using many things like NSNotificationCenter, then calculating the height of everything and making conditions makes this more complicated,

The Simple way to do this is coded below, it will work to move up the view.

func textFieldDidBeginEditing(_ textField: UITextField) {
        moveTextField(textfield: textField, moveDistance: -250, up: true)
}

func textFieldDidEndEditing(_ textField: UITextField) {
        moveTextField(textfield: textField, moveDistance: -250, up: false)
}

func moveTextField(textfield: UITextField, moveDistance: Int, up: Bool) {
    let moveDuration = 0.3
    let movement: CGFloat = CGFloat(up ? moveDistance: -moveDistance)
    UIView.beginAnimations("animateTextField", context: nil)
    UIView.setAnimationBeginsFromCurrentState(true)
    UIView.setAnimationDuration(moveDuration)
    self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
    UIView.commitAnimations()
}

you can change that -250 value according to the placement of your textfields.

R. Mohan
  • 2,182
  • 17
  • 30
2

In case you are using a UIScrollView or any of its subclasses, e.g., UITableView, you can also manipulate the contentInset property. That way you do not have to mess with frame, bounds, NSLayoutConstraint or NSLayoutAnchor.

func keyboardWillShow(notification: Notification) {
    let info = notification.userInfo!
    let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
    let keyboardHeight: CGFloat = keyboardSize.height
    let duration = info[UIKeyboardAnimationDurationUserInfoKey] as! TimeInterval
    UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
        self.tableView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
    }, completion: nil)
}

func keyboardWillHide(notification: Notification) {
    let info = notification.userInfo!
    let duration = info[UIKeyboardAnimationDurationUserInfoKey] as! TimeInterval
    UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
        self.tableView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }, completion: nil)
}

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

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

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Jens Meder
  • 4,237
  • 1
  • 25
  • 25
1

Swift 3.0

 var activeField: UITextField?

    override func viewDidLoad() {
        super.viewDidLoad()

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

    func textFieldDidBeginEditing(_ textField: UITextField){
        activeField = textField
    }

    func textFieldDidEndEditing(_ textField: UITextField){
        activeField = nil
    }

    func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
            if (self.activeField?.frame.origin.y)! >= keyboardSize.height {
                self.view.frame.origin.y = keyboardSize.height - (self.activeField?.frame.origin.y)!
            } else {
                self.view.frame.origin.y = 0
            }
        }
    }

    func keyboardWillHide(notification: NSNotification) {
        self.view.frame.origin.y = 0
    }
  • Welcome to Stack Overflow! Please don't add [the same answer](http://stackoverflow.com/a/42220193/4687348) to multiple questions. Answer the best one and flag the rest as duplicates. See [Is it acceptable to add a duplicate answer to several questions?](http://meta.stackexchange.com/q/104227/347985) – FelixSFD Feb 14 '17 at 07:57
1

In Swift 3 use this code

  override func viewDidLoad() {
        super.viewDidLoad()

                let center: NotificationCenter = NotificationCenter.default
        center.addObserver(self, selector: #selector(RFLogInViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        center.addObserver(self, selector: #selector(RFLogInViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
        }



 func keyboardWillShow(notification: NSNotification) {
        let info:NSDictionary = notification.userInfo! as NSDictionary
        let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue

        let keyboardHeight: CGFloat = keyboardSize.height

        let _: CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat


        UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {
            self.view.frame = CGRect(x: 0, y: (self.view.frame.origin.y - keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
        }, completion: nil)

    }




 func keyboardWillHide(notification: NSNotification) {
        let info: NSDictionary = notification.userInfo! as NSDictionary
        let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue

        let keyboardHeight: CGFloat = keyboardSize.height

        let _: CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat

        UIView.animate(withDuration: 0.25, delay: 0.25, options: .curveEaseInOut, animations: {

              self.view.frame = CGRect(x: 0, y: (self.view.frame.origin.y + keyboardHeight), width: self.view.bounds.width, height: self.view.bounds.height)
        }, completion: nil)

    }


  override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(true)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }
Nithinbemitk
  • 2,710
  • 4
  • 24
  • 27
1

Just add 'IQKeyboardManager' library into your project, and Done. You have not to do anything else. For reference please check this url.

https://github.com/hackiftekhar/IQKeyboardManager

aBilal17
  • 2,974
  • 2
  • 17
  • 23
1

Swift 4 I have seen numerous of answer and plenty of them did not work for me where I have a UIViewController With numerous of text fields.

According to the Apple documentation I have translate the example to Swift 4

Your content needs to be embedded within an scrollview.

Add the notification listeners for when the keyboard will appear or dissappear.

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

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: .UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    NotificationCenter.default.removeObserver(self)
}

Implement UITextField Delegates

func textFieldDidBeginEditing(_ textField: UITextField) {

    currentTextField = textField
}

func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {

    currentTextField = nil

}

Selectors

@objc func keyboardDidShow(notification: NSNotification) {
    print("\(logClassName): keyboardWDidShow")

    let keyboardFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue

    let keyboardSize:CGSize = keyboardFrame!.size

    let contentInsets:UIEdgeInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
    trackScrollView.contentInset = contentInsets
    trackScrollView.scrollIndicatorInsets = contentInsets

    var aRect:CGRect = self.view.frame
    aRect.size.height -= keyboardSize.height

    if !(aRect.contains(currentTextField!.frame.origin)){

        trackScrollView.scrollRectToVisible(currentTextField!.frame, animated: true)

    }

}

@objc func keyboardWillHide(notification: NSNotification){

    print("\(logClassName): keyboardWillHide")

    let contentInsents:UIEdgeInsets = UIEdgeInsets.zero
    trackScrollView.contentInset = contentInsents
    trackScrollView.scrollIndicatorInsets = contentInsents

}
Reimond Hill
  • 4,278
  • 40
  • 52
0

first of all you should have scrollview step 1:find the height of key board

func keyboardWillShow(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo ? [UIKeyboardFrameBeginUserInfoKey] as ? NSValue) ? .cgRectValue {
        let keyboardHeight = keyboardSize.height
        a = keyboardHeight
    }
}

step 2: bind the delegete methods of textfield

func textFieldDidBeginEditing(_ textField: UITextField) {
    utility.setUserDefaultBool(value: false, key: "FrameMoveFlag") //flag
    let b = view1.frame.height - textField.frame.origin.y
    if (b < 350) {
        view1.frame.origin.y = view1.frame.origin.y + (view1.frame.height - textField.frame.origin.y) - (a + 50)
        utility.setUserDefaultBool(value: true, key: "FrameMoveFlag") //flag
    }
}

func textFieldDidEndEditing(_ textField: UITextField) {
    if (utility.getUserDefaultBOOLForKey(key: "FrameMoveFlag") == true) {
        view1.frame.origin.y = 0
    }
}
Sibeesh Venu
  • 18,755
  • 12
  • 103
  • 140
0

Swift 3 code for Geiger answer

func textFieldDidBeginEditing(_ textField: UITextField) { // became first responder

    //move textfields up
    let myScreenRect: CGRect = UIScreen.main.bounds
    let keyboardHeight : CGFloat = 216

    UIView.beginAnimations( "animateView", context: nil)
    var movementDuration:TimeInterval = 0.35
    var needToMove: CGFloat = 0

    var frame : CGRect = self.view.frame
    if (textField.frame.origin.y + textField.frame.size.height + UIApplication.shared.statusBarFrame.size.height > (myScreenRect.size.height - keyboardHeight - 30)) {
        needToMove = (textField.frame.origin.y + textField.frame.size.height + UIApplication.shared.statusBarFrame.size.height) - (myScreenRect.size.height - keyboardHeight - 30);
    }

    frame.origin.y = -needToMove
    self.view.frame = frame
    UIView.commitAnimations()
}

func textFieldDidEndEditing(_ textField: UITextField) {
    //move textfields back down
    UIView.beginAnimations( "animateView", context: nil)
    var movementDuration:TimeInterval = 0.35
    var frame : CGRect = self.view.frame
    frame.origin.y = 0
    self.view.frame = frame
    UIView.commitAnimations()
}
Celil Bozkurt
  • 1,693
  • 16
  • 18
0

If you don't want to manually work with appearing and disappearing of the keyboard, just use the UITableViewController and it will handle all text fields in the table view.

DZoki019
  • 382
  • 2
  • 13
0

Check out my gist, I use a scrollView for my case, but it works for any kind of view, you only have to remove the scrollView part and replace it with your view. The gist is very well commented so you will also understand how this case is handled. https://gist.github.com/Sjahriyar/916e93153a29dc602b45f29d39182352

Shahriyar
  • 520
  • 7
  • 18
0

I created KeyboardController to handle the keyboard issue. All that needs to be done is call setUpKeyBoardListeners() and set the lastElement as whatever the last element in your view is.

Gist: https://gist.github.com/espitia/ef830cf677fa1bc33ffdf16ac12d0204

apolo
  • 441
  • 4
  • 18