How do I scroll the UIScrollView when the keyboard appears?
The recommended way from Apple is to change the contentInset
of the UIScrollView
. It is a very elegant solution, because you do not have to mess with the contentSize
. Following code is copied from the Keyboard Programming Guide, where the handling of this issue is explained. You should have a look into it.
// Call this method somewhere in your view controller setup code.- (void)registerForKeyboardNotifications{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];}// Called when the UIKeyboardDidShowNotification is sent.- (void)keyboardWasShown:(NSNotification*)aNotification{ NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0); scrollView.contentInset = contentInsets; scrollView.scrollIndicatorInsets = contentInsets; // If active text field is hidden by keyboard, scroll it so it's visible // Your application might not need or want this behavior. CGRect aRect = self.view.frame; aRect.size.height -= kbSize.height; if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) { CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y-kbSize.height); [scrollView setContentOffset:scrollPoint animated:YES]; }}// Called when the UIKeyboardWillHideNotification is sent - (void)keyboardWillBeHidden:(NSNotification*)aNotification{ UIEdgeInsets contentInsets = UIEdgeInsetsZero; scrollView.contentInset = contentInsets; scrollView.scrollIndicatorInsets = contentInsets;}
Swift version:
func registerForKeyboardNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardAppear(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardDisappear(_:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)}// Don't forget to unregister when donedeinit { NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardDidShow, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardDidHide, object: nil)}@objc func onKeyboardAppear(_ notification: NSNotification) { let info = notification.userInfo! let rect: CGRect = info[UIKeyboardFrameBeginUserInfoKey] as! CGRect let kbSize = rect.size let insets = UIEdgeInsetsMake(0, 0, kbSize.height, 0) scrollView.contentInset = insets scrollView.scrollIndicatorInsets = insets // If active text field is hidden by keyboard, scroll it so it's visible // Your application might not need or want this behavior. var aRect = self.view.frame; aRect.size.height -= kbSize.height; let activeField: UITextField? = [addressTextView, servicePathTextView, usernameTextView, passwordTextView].first { $0.isFirstResponder } if let activeField = activeField { if !aRect.contains(activeField.frame.origin) { let scrollPoint = CGPoint(x: 0, y: activeField.frame.origin.y-kbSize.height) scrollView.setContentOffset(scrollPoint, animated: true) } }}@objc func onKeyboardDisappear(_ notification: NSNotification) { scrollView.contentInset = UIEdgeInsets.zero scrollView.scrollIndicatorInsets = UIEdgeInsets.zero}
I just implemented this with Swift 2.0 for iOS9 on Xcode 7 (beta 6), works fine here.
override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) registerKeyboardNotifications()}func registerKeyboardNotifications() { NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)}deinit { NSNotificationCenter.defaultCenter().removeObserver(self)}func keyboardWillShow(notification: NSNotification) { let userInfo: NSDictionary = notification.userInfo! let keyboardSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue.size let contentInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0) scrollView.contentInset = contentInsets scrollView.scrollIndicatorInsets = contentInsets var viewRect = view.frame viewRect.size.height -= keyboardSize.height if CGRectContainsPoint(viewRect, textField.frame.origin) { let scrollPoint = CGPointMake(0, textField.frame.origin.y - keyboardSize.height) scrollView.setContentOffset(scrollPoint, animated: true) }}func keyboardWillHide(notification: NSNotification) { scrollView.contentInset = UIEdgeInsetsZero scrollView.scrollIndicatorInsets = UIEdgeInsetsZero}
Edited for Swift 3
Seems like you only need to set the contentInset
and scrollIndicatorInset
with Swift 3, the scrolling/contentOffset is done automatically..
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) registerKeyboardNotifications()}func registerKeyboardNotifications() { 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)}deinit { NotificationCenter.default.removeObserver(self)}func keyboardWillShow(notification: NSNotification) { let userInfo: NSDictionary = notification.userInfo! as NSDictionary let keyboardInfo = userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue let keyboardSize = keyboardInfo.cgRectValue.size let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0) scrollView.contentInset = contentInsets scrollView.scrollIndicatorInsets = contentInsets}func keyboardWillHide(notification: NSNotification) { scrollView.contentInset = .zero scrollView.scrollIndicatorInsets = .zero}
Swift 5 Solution:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) registerKeyboardNotifications()}func registerKeyboardNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)}override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self)}@objc func keyboardWillShow(notification: NSNotification) { let userInfo: NSDictionary = notification.userInfo! as NSDictionary let keyboardInfo = userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue let keyboardSize = keyboardInfo.cgRectValue.size let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0) scrollView.contentInset = contentInsets scrollView.scrollIndicatorInsets = contentInsets}@objc func keyboardWillHide(notification: NSNotification) { scrollView.contentInset = .zero scrollView.scrollIndicatorInsets = .zero}