web-dev-qa-db-fra.com

Faites défiler UITextField au-dessus du clavier dans une UITableViewCell sur un UIViewController normal

J'ai essayé la plupart des exemples ici sur StackOverflow. J'ai aussi utilisé Apple. Le problème que je semble avoir avec eux est qu’ils ne tiennent pas compte de la présence de UITextField dans une UITableView. Je l'ai fait plusieurs fois, mais pas de cette façon. J'ai un UITableViewCell personnalisé avec un UITextField.

Sur mon UITableView (qui n'est pas un UITableViewController), je dois pouvoir éviter de masquer UITextField sous UITableView.

J'ai ceci à partir de maintenant:

-(void)viewDidLoad
{
....
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:nil];

....
}

- (void)scrollToRectOfTextField {
    UITableViewCell *cell = (UITableViewCell*)[self.activeTextField superview];
    CGRect r = CGRectMake(self.activeTextField.frame.Origin.x,
                          cell.frame.Origin.y+self.activeTextField.frame.Origin.y,
                          self.activeTextField.frame.size.width,
                          self.activeTextField.frame.size.height);
    [self.tableView scrollRectToVisible:r animated:YES];
}

- (void)keyboardDidShow:(NSNotification *)notification
{
    if (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone) {
        NSDictionary *userInfo = [notification userInfo];
        CGSize size = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
        NSLog(@"TableView: %@", NSStringFromCGRect(self.tableView.frame));
        CGRect newTableViewFrame = CGRectMake(self.tableView.frame.Origin.x,
                                              self.tableView.frame.Origin.y,
                                              self.tableView.frame.size.width, self.tableView.frame.size.height - size.height);
        self.tableView.frame = newTableViewFrame;
        NSLog(@"New TableView: %@", NSStringFromCGRect(self.tableView.frame));


        self.tableView.contentSize = CGSizeMake(self.tableView.contentSize.width, self.tableView.contentSize.height-size.height);
    }
}


- (void)keyboardWillHide:(NSNotification *)notification
{
    NSDictionary *userInfo = [notification userInfo];
    CGSize size = [[userInfo objectForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    self.tableView.frame = CGRectMake(self.tableView.frame.Origin.x,
                                      self.tableView.frame.Origin.y,
                                      self.tableView.frame.size.width,
                                      self.tableView.frame.size.height + size.height);

    NSLog(@"%@", NSStringFromCGRect(self.tableView.frame));
}


-(void)textFieldDidBeginEditing
{
     [self scrollToRectOfTextField];
}

Cela semble pousser un tas d'espaces blancs au-delà de mon clavier. Veuillez également noter que j'ai également un inputAccessoryView sur mon UITextField.

Oh, et je ne peux utiliser aucune classe tierce. J'adore TPKeyboardAvoidingScrollView, mais je ne peux rien utiliser de tiers.

20
Matt Hudson

J'ai passé toute la journée à essayer de comprendre cela. Je l'ai posté ici, puis j'ai trouvé un lien vers un blog et une solution incroyablement simple. Cela ressemble à ceci:

-(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;
}

Cochez cette case pour iOS 8

49
Matt Hudson

J'ai essayé le lien que @inturbidus a publié pour iOS8, mais malheureusement, cela n'a pas fonctionné pour moi. Après quelques recherches, il s'avère qu'Apple a un exemple de code sur son site Web pour que cela fonctionne naturellement comme dans UITableViewController. Voici le lien Apple pour la version Objective-C et j'ajoute également la version Swift ici.

Lien Objective-C d’Apple (Sélectionnez le nom 5-1):https://developer.Apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html

Adaptation rapide de la version Objective-C 

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
}
21
Salman Hasrat Khan

Merci @Salman pour le lien.
Il suffit de remarquer que l’exemple Apple est utilisé pour le défilement dans une vue normale.
En mode tableau, vous devez convertir les points et rect de activeField en coordonnées de tableau, je modifie donc certaines lignes de la fonction 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)
        }
}

Et si votre vue contient tabBarController, n'oubliez pas de réinitialiser contentInsets avec tabBar height à la place de UIEdgeInsetsZero (sinon, vos dernières lignes inférieures peuvent être masquées sous 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
    }
}
7
Nhan Lam Dai

J'ai fait un mélange avec les réponses de Matt et aussi Salman . Cela fonctionne pour de nombreux champs de texte dans un tableau. Swift 3

Fondamentalement est d'obtenir le BOTTOM Y point du textInput touché. Une fois que nous avons ce que je vérifie si le clavier couvre le textInput et si je le fais je changerais le contentOffset de la tableView

Commencez par vous inscrire aux notifications. Dans init si est un UIView ou dans viewDidLoad si est un 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)

Puis configurez le champ de texte touché comme entrée courante

var inputActive: UITextField!

func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
    inputActive = textInput
    return true
}

Enfin, implémentez la méthode de notification:

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
}

Peu de choses à ajouter. Le padding est une distance supplémentaire que j’ajoute dans mon cas était de 20 points .

J'espère que cela t'aides!

7
Victor

Ma solution (travaillé sur Xcode 8.3.3, Swift 3, iOS 10.3.1):

class MyTableViewController: UITableViewController {

    override func viewWillAppear(_ animated: Bool) {
        NotificationCenter.default.addObserver(self, selector: #selector(actionKeyboardDidShow(with:)), name: .UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(actionKeyboardWillHide(with:)), name: .UIKeyboardWillHide, object: nil)
    }

    override func viewDidDisappear(_ animated: Bool) {
        NotificationCenter.default.removeObserver(self, name: .UIKeyboardDidShow, object: nil)
        NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
    }

    @objc private func actionKeyboardDidShow(with notification: Notification) {
        guard let userInfo = notification.userInfo as? [String: AnyObject],
            let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
            else { return }

        var contentInset = self.tableView.contentInset
        contentInset.bottom += keyboardFrame.height

        self.tableView.contentInset = contentInset
        self.tableView.scrollIndicatorInsets = contentInset
    }

    @objc private func actionKeyboardWillHide(with notification: Notification) {
        guard let userInfo = notification.userInfo as? [String: AnyObject],
            let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
            else { return }

        var contentInset = self.tableView.contentInset
        contentInset.bottom -= keyboardFrame.height

        self.tableView.contentInset = contentInset
        self.tableView.scrollIndicatorInsets = contentInset
    }

}
2
jqgsninimo

J'ai fait un mélange entre la réponse de @Victor et du code que j'avais… .. J'ai créé une extension pratique pouvant être utilisée sur de nombreux UITextField.

1.- Nous avons d’abord besoin d’une extension pour UITextField pour gérer les notifications au clavier

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.- Cette extension précédente doit être appelée dans textFieldShouldBeginEditing afin que nous puissions avoir une extension pour cela aussi

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.- Enfin, comme vous le remarquerez dans la méthode précédente, nous avons besoin de TableView où se trouve la cellule avec UITextField. Nous pouvons donc utiliser cette extension (qui est également utile à d'autres fins)

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.- Enfin dans la cellule où se trouve UITextField , nous écrivons simplement cette simple ligne de code

textField.delegate = textField;

Complètement réutilisable et universel.

1
Joaquin Pereira

Cette réponse s’adresse aux utilisateurs de Xamarin iOS, mais il est facile de la modifier pour la faire fonctionner avec Swift ou Objective C.

La solution est très simple et a parfaitement fonctionné dans mon cas où il me fallait une cellule pour apparaître dans la moitié supérieure de l'écran afin que le champ de texte de la cellule soit au-dessus du clavier. Donc ce que j'ai fait est le suivant:

J'ai implémenté textField.ShouldBeginEditing de la manière suivante:

myTextField.ShouldBeginEditing = textField =>
{
   myTableView.ScrollToRow(indexPath, UITableViewScrollPosition.Top, true);

   return true;
};

Où: 

myTextFieldest le champ de texte de la cellule,

myTableViewest la vue tabulaire,

indexPathest le chemin d'index de la cellule dans la vue Table.

Comme je l'ai déjà mentionné, cette solution fonctionne parfaitement dans mon cas, alors j'ai pensé que je pourrais la partager avec vous au cas où cela fonctionnerait pour vous également. Bonne codage :)

1
Georgios Iniatis

La solution de jqgsninimo a été mise à jour à la version 4.2. Fonctionne sur 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
}
1
Jayden Irwin

Faites défiler UITextField au-dessus du clavier dans une UITableViewCell sur un UIViewController standard Swift 3 et Xcode 8.1  

1) définir Delegate = UITextFieldDelegate 

  override func viewWillAppear(_ animated: Bool) {

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

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


//MARK: - Keyboard Show and Hide Methods
    func keyboardWillShow(notification: NSNotification)
    {
        let info = notification.userInfo! as! [String: AnyObject],
        kbSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue.size,
        contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)

        self.FourthTblMainTableView.contentInset = contentInsets
        self.FourthTblMainTableView.scrollIndicatorInsets = contentInsets

        var aRect = self.FourthTblMainTableView.frame
        aRect.size.height -= kbSize.height
    }
    func keyboardWillHide(notification: NSNotification)
    {
         let contentInsets = UIEdgeInsets.zero
        self.FourthTblMainTableView.contentInset = contentInsets
        self.FourthTblMainTableView.scrollIndicatorInsets = contentInsets
    }
1
Parth Changela

Dans Swift4, nous pouvons créer une extension de UIViewController.

extension UIViewController {
    //MARK: Keyboard user interactions
    func handleContainerViewFrameOnKeyboardShowHide(originalContainerFrame: CGRect) {
        NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillShow, object: nil, queue: OperationQueue.main) {(notification) in
            var info = (notification as NSNotification).userInfo!
            let keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
            var tableViewFrame = originalContainerFrame
            tableViewFrame.size.height = originalContainerFrame.size.height - keyboardFrame.size.height
            self.view.frame = tableViewFrame
        }
        NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: OperationQueue.main) { (notification) in
            self.view.frame = originalContainerFrame
        }
    }
}
0
Susim Samanta