Scroll UITextField above Keyboard in a UITableViewCell on a regular UIViewController

前端 未结 10 1028
被撕碎了的回忆
被撕碎了的回忆 2020-11-27 14:17

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

相关标签:
10条回答
  • 2020-11-27 14:25

    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

    0 讨论(0)
  • 2020-11-27 14:27

    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
    }
    
    0 讨论(0)
  • 2020-11-27 14:28

    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.

    0 讨论(0)
  • 2020-11-27 14:29

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-27 14:31

    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!

    0 讨论(0)
  • 2020-11-27 14:33

    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
    }
    
    0 讨论(0)
提交回复
热议问题