How to make UITextField move up when keyboard is present? - ios
How do I prevent a UITextField from being hidden by the keyboard?
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)
}
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()
}
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.
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)
}
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
}
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)
}
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
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
}
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
}
}
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()
}
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.
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
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
Related
How to make the UITextView keyboard don't dismiss by tapping_Swift
I have a problem about the keyboard. If I want to keep showing keyboard when I tap, and what should I do. I also mark the addObserver function "keyboardWillHide", but the keyboard also dismiss. Thanks. This is code: override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:.UIKeyboardWillShow, object: nil) //NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:.UIKeyboardWillHide, object: nil) } override func keyboardWillShow(notification: NSNotification) { super.keyboardWillShow(notification: notification) if let userInfo:NSDictionary = notification.userInfo as NSDictionary? { if let keyboardFrame:NSValue = userInfo.value(forKey: UIKeyboardFrameEndUserInfoKey) as? NSValue { keyboardHeight = keyboardFrame.cgRectValue.height } } setUserMode(mode: .keyboard) updateViewFrame() } override func keyboardWillHide(notification: NSNotification) { keyboardHeight = 0 updateViewFrame() } func updateViewFrame() { var diffHeight:CGFloat = UIApplication.shared.statusBarFrame.height if let nav = navigationController { diffHeight += nav.navigationBar.height } let newFrame = CGRect(x: 0, y: diffHeight, width: ScreenWidth, height: ScreenHeight-keyboardHeight-diffHeight) if view.frame.equalTo(newFrame) == false { view.frame = newFrame self.tableView.frame = newFrame } } Image here:
Just call : YourTextView.keyboardDismissMode = .none
How to fix position of UIView when keyboard moved up?
How do I fix the position of UIView with image and label when the keyboard is moved up? I made a code to move up my textField when the keyboard shows, but instead this moves everything. func textFieldDidEndEditing(_ textField: UITextField) { moveTextField(textField: rasstoyanietextField, moveDistance: -215, up: false) } func textFieldDidBeginEditing(_ textField: UITextField) { moveTextField(textField: rasstoyanietextField, moveDistance: -215, up: true) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { rasstoyanietextField.resignFirstResponder() return true } func moveTextField(textField: UITextField, moveDistance: Int, up: Bool){ let moveDuration = 0.1 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() } How could I fix it?
Because you move whole view, instead of textview only. Change line: self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement) with: textField.frame = textField.frame.offsetBy(dx: 0, dy: movement)
Add keybaord observers NotificationCenter.default.addObserver( self, selector: #selector(handleKeyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil) NotificationCenter.default.addObserver( self, selector: #selector(handleKeyboardWillHide), name:NSNotification.Name.UIKeyboardWillHide, object: nil) I think it's better to use scrollviews in viewControllers that have textfeilds/textViews to move up all items when showing but here you may Hook the bottom constraint of the textfeild or it's superview and drag it as IBOutlet and do this #objc func handleKeyboardDidShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { self.textFeilfBottomCon.constant = -1 * keyboardSize.height UIView.animate(withDuration: 0.5, animations: { self.view.layoutIfNeeded() }) } } #objc func handleKeyboardWillHide(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { self.textFeilfBottomCon.constant = 0 UIView.animate(withDuration: 0.5, animations: { self.view.layoutIfNeeded() }) } }
Move UITextField up when keyboard appears
I am trying to write a code to ensure UITextField move up when keyboard present. I have written a code based on what I found in stack overflow however it is not working. The original code was written in objective-c but I am not familiar with the it and hence decided to to write code in swift. can anyone tell what I may be doing wring here? { // moving text box up when tyoing func registerForKeyboardNotifications() { //Adding notifies on keyboard appearing NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) } #objc func keyboardWasShown(notification: NSNotification) { //Need to calculate keyboard exact size due to Apple suggestions self.scrollView.isScrollEnabled = true let info : NSDictionary = notification.userInfo! as NSDictionary let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0) self.scrollView.contentInset = contentInsets self.scrollView.scrollIndicatorInsets = contentInsets var aRect : CGRect = self.view.frame aRect.size.height -= keyboardSize!.height if (yourEmail) != nil { if (!aRect.contains(yourEmail!.frame.origin)) { self.scrollView.scrollRectToVisible(yourEmail!.frame, animated: true) } } } #objc func keyboardWillBeHidden(notification: NSNotification) { //Once keyboard disappears, restore original positions let info : NSDictionary = notification.userInfo! as NSDictionary as NSDictionary let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0) self.scrollView.contentInset = contentInsets self.scrollView.scrollIndicatorInsets = contentInsets self.view.endEditing(true) self.scrollView.isScrollEnabled = false } func textFieldDidBeginEditing(_ textField: UITextField) { yourEmail = textField } func textFieldDidEndEditing(_ textField: UITextField) { yourEmail = nil } }
Try this code func textFieldDidBeginEditing(textField: UITextField!) { self.scrollView.setContentOffset(CGPoint.init(x: 0, y: scrollBy), animated: true) // scrollBy - pass the height you want your scrollview to be scrolled when keyboard appears } func textFieldDidEndEditing(textField: UITextField!) { self.scrollView.setContentOffset(CGPoint.init(x: 0, y: 0), animated: true) self.view.endEditing(true); }
below is the code I wrote with the help of suggestions: { func textFieldDidBeginEditing(_ textField: UITextField) { if textField.frame.maxY > self.view.frame.height * 0.6 { self.scrollView.setContentOffset(CGPoint.init(x: 0, y: textField.frame.maxY - self.view.frame.height * 0.6 + 2.0), animated: true) } else{ return } } func textFieldDidEndEditing(_ textField: UITextField) { self.scrollView.setContentOffset(CGPoint.init(x: 0, y: 0), animated: true) self.view.endEditing(true); } }
UIScrollView scroll up on Keyboard
Problem Statement : I have Nib file of UIView contains UIScrollview, In scrollview I have several TextFields and a TextView at the Bottom. What I want is to scroll upward when textfield or Textview starts editing. What I tried : In custome Method NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) I am calling this method parent view. Notification Handling: func keyboardWasShown(notification: NSNotification) { var userInfo = notification.userInfo! var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.convert(keyboardFrame, from: nil) var contentInset:UIEdgeInsets = self.mainScroll.contentInset contentInset.bottom = keyboardFrame.size.height self.mainScroll.contentInset = contentInset } This is working perfectly for UITextFields, But not working UITextView. Any idea where is the mistake. PS: I have set the Delegates of UITextField and UITextView as well. Any help will be much appreciated.
Replace keyboardWasShown function with the below function : func keyboardWasShown(notification: NSNotification) { var userInfo = notification.userInfo! var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.convert(keyboardFrame, from: nil) self.mainScroll.contentOffset = CGPoint(x: 0, y: keyboardFrame.size.height - Any number that fits your need.) } In keyBoardWillHide : self.mainScroll.contentOffset = CGPoint(x: 0, y: 0) Hope it will help. Happy Coding!
Demo link : https://github.com/harshilkotecha/UIScrollViewWhenKeyboardAppearInSwift3 when you have multiple textview it is so difficult so best solution -> step 1 : add UITextFieldDelegate class ScrollViewController: UIViewController,UITextFieldDelegate { step 2 :create new IBOutlet but don't connect with any text field // get current text box when user Begin editing #IBOutlet weak var activeTextField: UITextField? step 3 : write this two method when user focus on text filed object pass the reference and store in activeTextField // get current text field func textFieldDidBeginEditing(_ textField: UITextField) { activeTextField=textField; } func textFieldDidEndEditing(_ textField: UITextField) { activeTextField=nil; } step 5 : set Notification in viewdidload setNotificationKeyboard override func viewWillAppear(_ animated: Bool) { // call method for keyboard notification self.setNotificationKeyboard() } // Notification when keyboard show func setNotificationKeyboard () { NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: .UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: .UIKeyboardWillHide, object: nil) } step 6 : two methods for hide and show keyboard func keyboardWasShown(notification: NSNotification) { var info = notification.userInfo! let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height+10, 0.0) self.scrollView.contentInset = contentInsets self.scrollView.scrollIndicatorInsets = contentInsets var aRect : CGRect = self.view.frame aRect.size.height -= keyboardSize!.height if let activeField = self.activeTextField { if (!aRect.contains(activeField.frame.origin)) { self.scrollView.scrollRectToVisible(activeField.frame, animated: true) } } } // when keyboard hide reduce height of scroll view func keyboardWillBeHidden(notification: NSNotification){ let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0,0.0, 0.0) self.scrollView.contentInset = contentInsets self.scrollView.scrollIndicatorInsets = contentInsets self.view.endEditing(true) }
Swift 5 NotificationCenter.default.addObserver(self, selector: #selector(keyboardNotification), name: UIResponder.keyboardDidChangeFrameNotification, object: nil) #objc func keyboardNotification(_ notification: Notification) { if let userInfo = (notification as NSNotification).userInfo { let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue let duration:TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0 let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions().rawValue let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw) if (endFrame?.origin.y)! >= UIScreen.main.bounds.size.height { scrollViewBottomConstraint?.constant = 0 } else { if tabBarController?.tabBar.frame == nil { return } scrollViewBottomConstraint?.constant = endFrame!.size.height - (tabBarController?.tabBar.frame.height)! let bottomOffset = CGPoint(x: 0, y: 0) scrollView.setContentOffset(bottomOffset, animated: true) } UIView.animate(withDuration: duration, delay: 0, options: animationCurve, animations: { self.view.layoutIfNeeded() }, completion: nil) } } usage: 1.connect the scrollView outlet to your controller and set its name to 'scrollView' 2.connect the scrollView bottom constraint to your controller & set its name to 'scrollViewBottomConstraint' 3.put the notification observer to your ViewDidLoad function.
ScrollView and keyboard in Swift
I started creating a simple iOS app that does some operations. But I'm having some problems when the keyboard appears, hiding one of my textfields. I think it's a common problem and I did some research but I couldn't find anything that solved my problem. I want to use a ScrollView rather than animate the textfield to make it visible.
In ViewDidLoad, register the notifications: NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil) Add below observer methods which does the automatic scrolling when keyboard appears. #objc func keyboardWillShow(notification:NSNotification) { guard let userInfo = notification.userInfo else { return } var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.view.convert(keyboardFrame, from: nil) var contentInset:UIEdgeInsets = self.scrollView.contentInset contentInset.bottom = keyboardFrame.size.height + 20 scrollView.contentInset = contentInset } #objc func keyboardWillHide(notification:NSNotification) { let contentInset:UIEdgeInsets = UIEdgeInsets.zero scrollView.contentInset = contentInset }
The top answer for swift 3: NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:NSNotification.Name.UIKeyboardWillHide, object: nil) And then: func keyboardWillShow(notification:NSNotification){ //give room at the bottom of the scroll view, so it doesn't cover up anything the user needs to tap var userInfo = notification.userInfo! var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.view.convert(keyboardFrame, from: nil) var contentInset:UIEdgeInsets = self.theScrollView.contentInset contentInset.bottom = keyboardFrame.size.height theScrollView.contentInset = contentInset } func keyboardWillHide(notification:NSNotification){ let contentInset:UIEdgeInsets = UIEdgeInsets.zero theScrollView.contentInset = contentInset }
Here is a complete solution, utilizing guard and concise code. Plus correct code in keyboardWillHide to only reset the bottom to 0. #IBOutlet private weak var scrollView: UIScrollView! override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) registerNotifications() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) scrollView.contentInset.bottom = 0 } private func registerNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) } #objc private func keyboardWillShow(notification: NSNotification){ guard let keyboardFrame = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return } scrollView.contentInset.bottom = view.convert(keyboardFrame.cgRectValue, from: nil).size.height } #objc private func keyboardWillHide(notification: NSNotification){ scrollView.contentInset.bottom = 0 }
for Swift 4.0 In ViewDidLoad // setup keyboard event 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) Add below observer methods which does the automatic scrolling when keyboard appears. #objc func keyboardWillShow(notification:NSNotification){ var userInfo = notification.userInfo! var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.view.convert(keyboardFrame, from: nil) var contentInset:UIEdgeInsets = self.ui_scrollView.contentInset contentInset.bottom = keyboardFrame.size.height ui_scrollView.contentInset = contentInset } #objc func keyboardWillHide(notification:NSNotification){ let contentInset:UIEdgeInsets = UIEdgeInsets.zero ui_scrollView.contentInset = contentInset }
contentInset doesn't work for me, because I want the scrollview move all the way up above the keyboard. So I use contentOffset: func keyboardWillShow(notification:NSNotification) { guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else { return } let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil) scrollView.contentOffset = CGPoint(x:0, y:keyboardFrame.size.height) } func keyboardWillHide(notification:NSNotification) { scrollView.contentOffset = .zero }
Swift 5 Only adjust ScrollView when TextField is hidden by keyboard (for multiple TextFields) Add / Remove Observers: override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self) } Keep track of these values so you can return to your original position: var scrollOffset : CGFloat = 0 var distance : CGFloat = 0 Adjust ScrollView contentOffset depending on TextField Location: #objc func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { var safeArea = self.view.frame safeArea.size.height += scrollView.contentOffset.y safeArea.size.height -= keyboardSize.height + (UIScreen.main.bounds.height*0.04) // Adjust buffer to your liking // determine which UIView was selected and if it is covered by keyboard let activeField: UIView? = [textFieldA, textViewB, textFieldC].first { $0.isFirstResponder } if let activeField = activeField { if safeArea.contains(CGPoint(x: 0, y: activeField.frame.maxY)) { print("No need to Scroll") return } else { distance = activeField.frame.maxY - safeArea.size.height scrollOffset = scrollView.contentOffset.y self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true) } } // prevent scrolling while typing scrollView.isScrollEnabled = false } } #objc func keyboardWillHide(notification: NSNotification) { if distance == 0 { return } // return to origin scrollOffset self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset), animated: true) scrollOffset = 0 distance = 0 scrollView.isScrollEnabled = true } Make sure to use [UIResponder.keyboardFrameEndUserInfoKey] to get the proper keyboard height the first time.
From the answer by Sudheer Palchuri, converted for Swift 4. In ViewDidLoad, register the notifications: NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:NSNotification.Name.UIKeyboardWillHide, object: nil) And then: // MARK: - Keyboard Delegates func textFieldShouldReturn(textField: UITextField) -> Bool { textField.resignFirstResponder() return true } #objc func keyboardWillShow(notification:NSNotification){ var userInfo = notification.userInfo! var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.view.convert(keyboardFrame, from: nil) var contentInset:UIEdgeInsets = self.scrollView.contentInset contentInset.bottom = keyboardFrame.size.height self.scrollView.contentInset = contentInset } #objc func keyboardWillHide(notification:NSNotification){ let contentInset:UIEdgeInsets = UIEdgeInsets.zero self.scrollView.contentInset = contentInset }
Reading the links you sent to me, I found a way to make it work, thanks!: func textFieldDidBeginEditing(textField: UITextField) { if (textField == //your_field) { scrollView.setContentOffset(CGPointMake(0, field_extra.center.y-280), animated: true) callAnimation() viewDidLayoutSubviews() } } func textFieldDidEndEditing(textField: UITextField) { if (textField == //your_field){ scrollView .setContentOffset(CGPointMake(0, 0), animated: true) viewDidLayoutSubviews() } }
This is a refinement on Zachary Probst solution posted above. I ran into a few issues with his solution and fixed it and enhanced it a bit. This version does not need to pass in a list of UITextView controls. It finds the first responder in the current view. It also handles UITextView controls at any level in the View hierarchy. I think his safeArea calculation wasn't quite right scrollView.contentOffset.y needed sign changed. It didn't show up if it was not scrolled. This fixed incremental scrolling. It might have been from other changes I made. This works if the user jumps around to other UITextViews while the keyboard is up. This is a Base Class I use for a bunch of ViewControllers. The inherited ViewController just needs to set the UIScrollViewer which activates this code behavior. class ThemeAwareViewController: UIViewController { var scrollViewForKeyBoard: UIScrollView? = nil var saveOffsetForKeyBoard: CGPoint? func findViewThatIsFirstResponder(view: UIView) -> UIView? { if view.isFirstResponder { return view } for subView in view.subviews { if let hit = findViewThatIsFirstResponder(view: subView) { return hit } } return nil } #objc func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { if let scrollView = scrollViewForKeyBoard { var safeArea = self.view.frame safeArea.size.height -= scrollView.contentOffset.y safeArea.size.height -= keyboardSize.height safeArea.size.height -= view.safeAreaInsets.bottom let activeField: UIView? = findViewThatIsFirstResponder(view: view) if let activeField = activeField { // This line had me stumped for a while (I was passing in .frame) let activeFrameInView = view.convert(activeField.bounds, from: activeField) let distance = activeFrameInView.maxY - safeArea.size.height if saveOffsetForKeyBoard == nil { saveOffsetForKeyBoard = scrollView.contentOffset } scrollView.setContentOffset(CGPoint(x: 0, y: distance), animated: true) } } } } #objc func keyboardWillHide(notification: NSNotification) { guard let restoreOffset = saveOffsetForKeyBoard else { return } if let scrollView = scrollViewForKeyBoard { scrollView.setContentOffset(restoreOffset, animated: true) self.saveOffsetForKeyBoard = nil } } }
You can animate your scrollview to center on your UITextField on keyboard appearance (ie. making your textfield the first responder) via a scroll offset. Here are a couple of good resources to get you started (there are a bunch on this site): How programmatically move a UIScrollView to focus in a control above keyboard? How to make a UIScrollView auto scroll when a UITextField becomes a first responder Additionally, if you simply use a UITableView with your content in cells, when the textfield becomes first responder, the UITableViewController will automatically scroll to the textfield cell for you (though I'm not sure this is what you want to do).
An answer for Swift 3, based on the one proposed by Daniel Jones, but safer (thanks to the guard), more concise and with consistent scroll indicator insets: #objc private func keyboardWillBeShown(notification: NSNotification) { guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else { return } let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil) scrollView.contentInset.bottom = keyboardFrame.size.height scrollView.scrollIndicatorInsets = scrollView.contentInset } #objc private func keyboardWillBeHidden() { scrollView.contentInset = .zero scrollView.scrollIndicatorInsets = scrollView.contentInset }
In Swift4, just add the following extension. extension UIViewController { func setupViewResizerOnKeyboardShown() { NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShowForResizing), name: Notification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHideForResizing), name: Notification.Name.UIKeyboardWillHide, object: nil) } #objc func keyboardWillShowForResizing(notification: Notification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let window = self.view.window?.frame { // We're not just minusing the kb height from the view height because // the view could already have been resized for the keyboard before self.view.frame = CGRect(x: self.view.frame.origin.x, y: self.view.frame.origin.y, width: self.view.frame.width, height: window.origin.y + window.height - keyboardSize.height) } else { debugPrint("We're showing the keyboard and either the keyboard size or window is nil: panic widely.") } } #objc func keyboardWillHideForResizing(notification: Notification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { let viewHeight = self.view.frame.height self.view.frame = CGRect(x: self.view.frame.origin.x, y: self.view.frame.origin.y, width: self.view.frame.width, height: viewHeight + keyboardSize.height) } else { debugPrint("We're about to hide the keyboard and the keyboard size is nil. Now is the rapture.") } } }
There is a simple solution here
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. 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:Notification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0) } } func keyboardWillHide(_ notification:Notification) { if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) } }
In case anyone is looking for Objective-C code for this solution: - (void)keyboardWasShown:(NSNotification *)notification { NSDictionary* info = [notification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; UIEdgeInsets contentInsets = baseScrollView.contentInset; contentInsets.bottom = kbSize.height; baseScrollView.contentInset = contentInsets; } - (void)keyboardWillBeHidden:(NSNotification *)notification { UIEdgeInsets contentInsets = UIEdgeInsetsZero; baseScrollView.contentInset = contentInsets; [baseScrollView endEditing:YES]; }
In my situation it was ScrollView --> TableView --> TableViewCell So I had to get y position in relative to keyboard frame and check if keyboard y position and my active field y position was intersecting or not #objc func keyboardWillShow(_ notification: Foundation.Notification) { var userInfo = notification.userInfo! var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue keyboardFrame = self.view.convert(keyboardFrame, from: nil) var contentInset:UIEdgeInsets = self.scrollView!.contentInset contentInset.bottom = keyboardFrame.size.height let loc = self.activeTextField?.convert(activeTextField!.bounds, to: self.view) if keyboardFrame.origin.y < loc!.origin.y { self.scrollView?.contentOffset = CGPoint.init(x: (self.scrollView?.contentOffset.x)!, y: loc!.origin.y) } if self.scrollView?.contentInset.bottom == 0 { self.scrollView?.contentInset = contentInset } }