I have tried most of the examples here on StackOverflow. I also used Apple\'s. The problem I seem to have with them is that they don\'t account for the UITextField being in
I spent all day trying to figure this out. I posted it here, then found a blog link and an incredibly simple solution. It looks like this:
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
CGPoint pointInTable = [textField.superview convertPoint:textField.frame.origin toView:self.tableView];
CGPoint contentOffset = self.tableView.contentOffset;
contentOffset.y = (pointInTable.y - textField.inputAccessoryView.frame.size.height);
NSLog(@"contentOffset is: %@", NSStringFromCGPoint(contentOffset));
[self.tableView setContentOffset:contentOffset animated:YES];
return YES;
}
-(BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[textField resignFirstResponder];
if ([textField.superview.superview isKindOfClass:[UITableViewCell class]])
{
UITableViewCell *cell = (UITableViewCell*)textField.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:TRUE];
}
return YES;
}
Check this for iOS 8
jqgsninimo's solution updated to 4.2. Works on iOS 12.0.
Swift 4.2
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(with:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(with:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc func keyboardDidShow(with notification: Notification) {
guard let userInfo = notification.userInfo as? [String: AnyObject],
let keyboardFrame = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
else { return }
var contentInset = self.tableView.contentInset
contentInset.bottom += keyboardFrame.height
tableView.contentInset = contentInset
tableView.scrollIndicatorInsets = contentInset
}
@objc func keyboardWillHide(with notification: Notification) {
guard let userInfo = notification.userInfo as? [String: AnyObject],
let keyboardFrame = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
else { return }
var contentInset = self.tableView.contentInset
contentInset.bottom -= keyboardFrame.height
tableView.contentInset = contentInset
tableView.scrollIndicatorInsets = contentInset
}
I did a mix between the answer of @Victor and some code that I had. I created a handy extension that can be use across many UITextField.
1.- First we need an extension for the UITextField to manage the keyboard notifications
extension UITextField {
func keepTextFieldAboveKeyboard(tableView:UITableView) {
var willShowNotification: NSObjectProtocol?
var willHideNotification: NSObjectProtocol?
willShowNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillShow, object: nil, queue: OperationQueue.main) {(notification) in
var userInfo = notification.userInfo!
if let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
// Get my height size
let myheight = tableView.frame.height
// Get the top Y point where the keyboard will finish on the view
let keyboardEndPoint = myheight - keyboardFrame.height
// Get the the bottom Y point of the textInput and transform it to the currentView coordinates.
if let pointInTable = self.superview?.convert(self.frame.origin, to: tableView) {
let textFieldBottomPoint = pointInTable.y + self.frame.size.height + 20
// Finally check if the keyboard will cover the textInput
if keyboardEndPoint <= textFieldBottomPoint {
tableView.contentOffset.y = textFieldBottomPoint - keyboardEndPoint
} else {
tableView.contentOffset.y = 0
}
}
}
NotificationCenter.default.removeObserver(willShowNotification!)
}
willHideNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: OperationQueue.main) { (notification) in
tableView.contentOffset.y = 0
NotificationCenter.default.removeObserver(willHideNotification!)
}
}
}
2.- This previous extension should be call inside textFieldShouldBeginEditing so we can have an extension for that as well
extension UITextField : UITextFieldDelegate {
public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
guard let tableView = textField.parentView(of: UITableView.self) else { return true };
textField.keepTextFieldAboveKeyboard(tableView:tableView);
return true;
}
}
3.- Finally as you notice in the previous method, we need the TableView where the cell with the UITextField is located. So we can use this extension (Which is also useful for other purposes)
extension UIView {
func parentView<T: UIView>(of type: T.Type) -> T? {
guard let view = self.superview else {
return nil
}
return (view as? T) ?? view.parentView(of: T.self)
}
}
4.- Finally in the cell where the UITextField is located, we just write this simple line of code
textField.delegate = textField;
Completely reusable and universal.
Thank @Salman for the link.
Just notice that Apple example is used for scrollview in normal view.
In table view you must convert Point and Rect of activeField to table's coordinate, so I change some lines in function keyboardWasShown:
func keyboardWasShown(aNotification: NSNotification) {
let info = aNotification.userInfo as! [String: AnyObject],
kbSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue().size,
contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
self.tableBasket.contentInset = contentInsets
self.tableBasket.scrollIndicatorInsets = contentInsets
var aRect = self.view.frame
aRect.size.height -= kbSize.height
let pointInTable = activeField!.superview!.convertPoint(activeField!.frame.origin, toView: tableView)
let rectInTable = activeField!.superview!.convertRect(activeField!.frame, toView: tableView)
if !CGRectContainsPoint(aRect, pointInTable) {
self.tableView.scrollRectToVisible(rectInTable, animated: true)
}
}
And if your view has tabBarController, remember to reset contentInsets with tabBar height instead of UIEdgeInsetsZero (if not, your last bottom rows maybe hiden under tabBar):
func keyboardWillBeHidden(aNotification: NSNotification) {
//UIEdgeInsetsZero is used in view without tabBar
//let contentInsets = UIEdgeInsetsZero
let tabBarHeight = self.tabBarController!.tabBar.frame.height
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: tabBarHeight, right: 0)
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
}
}
I made a mix with the answers of Matt and also Salman. This works for many textfields in a tableView. Swift 3
Basically is get the BOTTOM Y point of the textInput touched. Once we have that I check if the keyboard cover the textInput and if do it I would change the contentOffset of the tableView
First register to the notifications. In the init if is a UIView or in viewDidLoad if is a UIViewController:
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)
Then set up the textfield touched as current input
var inputActive: UITextField!
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
inputActive = textInput
return true
}
Finally implement the notifications method:
func keyboardWillShow(notification: NSNotification) {
var userInfo = notification.userInfo!
if let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
// Get my height size
let myheight = tableView.frame.height
// Get the top Y point where the keyboard will finish on the view
let keyboardEndPoint = myheight - keyboardFrame.height
// Get the the bottom Y point of the textInput and transform it to the currentView coordinates.
if let pointInTable = inputActive.superview?.convert(inputActive.frame.origin, to: tableView) {
let textFieldBottomPoint = pointInTable.y + inputActive.frame.size.height + 20
// Finally check if the keyboard will cover the textInput
if keyboardEndPoint <= textFieldBottomPoint {
tableView.contentOffset.y = textFieldBottomPoint - keyboardEndPoint
} else {
tableView.contentOffset.y = 0
}
}
}
}
func keyboardWillHide(notification: NSNotification) {
tableView.contentOffset.y = 0
}
Few things to add. The padding is some extra distance that I add in my case was 20 points. Also in my case the UITableView was added to a UIViewController but this should work also in a UITableViewController
Hope this helps!
I tried the link that @inturbidus posted for iOS8 but unfortunately it did not work for me. After a bit of digging, it turns out that Apple does have sample code on their website to make this work naturally as they do in the UITableViewController. Here's the Apple link for the Objective-C version and I'm also adding the swift version here.
Objective-C link from Apple (Scroll to listing 5-1): https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
Swift adaptation of the Objective-C Version
var activeField: UITextField?
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = nil
}
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(aNotification: NSNotification) {
let info = aNotification.userInfo as! [String: AnyObject],
kbSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue().size,
contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect = self.view.frame
aRect.size.height -= kbSize.height
if !CGRectContainsPoint(aRect, activeField!.frame.origin) {
self.tableView.scrollRectToVisible(activeField!.frame, animated: true)
}
}
func keyboardWillBeHidden(aNotification: NSNotification) {
let contentInsets = UIEdgeInsetsZero
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
}