46 votes

Faire en sorte que le UITableView défile jusqu'au champ UITextField sélectionné et évite d'être masqué par le clavier.

J'ai un UITextField dans une vue de table sur un UIViewController (pas un UITableViewController ). Si la vue de la table est sur un UITableViewController le tableau défilera automatiquement jusqu'au textField en cours d'édition pour éviter qu'elle ne soit masquée par le clavier. Mais sur un UIViewController ce n'est pas le cas.

J'ai essayé pendant deux jours de lire les différentes façons d'y parvenir, mais je n'y arrive pas. La chose la plus proche qui défile réellement est.. :

-(void) textFieldDidBeginEditing:(UITextField *)textField {

// SUPPOSEDLY Scroll to the current text field

CGRect textFieldRect = [textField frame];
[self.wordsTableView scrollRectToVisible:textFieldRect animated:YES];

}

Cependant, cela ne fait défiler le tableau que jusqu'à la ligne la plus haute. Ce qui semble être une tâche facile s'est avéré être quelques jours de frustration.

J'utilise ce qui suit pour construire les cellules de la tableView :

- (UITableViewCell *)tableView:(UITableView *)aTableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {

NSString *identifier = [NSString stringWithFormat: @"%d:%d", [indexPath indexAtPosition: 0], [indexPath indexAtPosition:1]];

UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:identifier];

    if (cell == nil) {

        cell = [[[UITableViewCell alloc] 
        initWithStyle:UITableViewCellStyleDefault 
        reuseIdentifier:identifier] autorelease];

        cell.accessoryType = UITableViewCellAccessoryNone;

        UITextField *theTextField = [[UITextField alloc] initWithFrame:CGRectMake(180, 10, 130, 25)];

        theTextField.adjustsFontSizeToFitWidth = YES;
        theTextField.textColor = [UIColor redColor];
        theTextField.text = [textFieldArray objectAtIndex:indexPath.row];
        theTextField.keyboardType = UIKeyboardTypeDefault;
        theTextField.returnKeyType = UIReturnKeyDone;
        theTextField.font = [UIFont boldSystemFontOfSize:14];
        theTextField.backgroundColor = [UIColor whiteColor];
        theTextField.autocorrectionType = UITextAutocorrectionTypeNo;
        theTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
        theTextField.clearsOnBeginEditing = NO;
        theTextField.textAlignment = UITextAlignmentLeft;

        //theTextField.tag = 0;
        theTextField.tag=indexPath.row;

        theTextField.delegate = self;

        theTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
        [theTextField setEnabled: YES];

        [cell addSubview:theTextField];

        [theTextField release];

}

return cell;
}

Je pense que je peux faire défiler correctement le tableView si je peux passer l'option indexPath.row dans le textFieldDidBeginEditing méthode ?

Toute aide est appréciée.

109voto

Andrei Stanescu Points 3424

Dans mon application, j'ai utilisé avec succès une combinaison des éléments suivants contentInset y scrollToRowAtIndexPath comme ça :

Lorsque vous voulez afficher le clavier, il suffit d'ajouter un contentInset en bas avec votre tableau à la hauteur désirée :

tableView.contentInset =  UIEdgeInsetsMake(0, 0, height, 0);

Ensuite, vous pouvez utiliser en toute sécurité

[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:cell_index inSection:cell_section] animated:YES];

En ajoutant le contentInset, même si vous vous concentrez sur la dernière cellule, le tableView sera toujours capable de défiler. Veillez simplement à réinitialiser le contentInset lorsque vous quittez le clavier.

EDIT :
Si vous n'avez qu'une seule section (vous pouvez remplacer cell_section avec 0) et utiliser la balise textView pour informer la rangée de cellules.

0 votes

Voulez-vous dire : tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, height) ;

0 votes

Désolé, c'est ma faute. J'ai tapé ça de mémoire. J'ai modifié mon message pour corriger cela

0 votes

La bonne nouvelle est que maintenant, lorsque j'utilise : [[wordsTableView scrollToRowAtIndexPath :[NSIndexPath indexPathForRow:5 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES] ; je réussis effectivement à faire défiler le tableView jusqu'à la ligne 5. Mais ce dont j'ai besoin, c'est de savoir quelle ligne est invoquée.

49voto

FunkyKat Points 1488

Swift

@objc private func keyboardWillShow(_ notification: Notification) {
    guard let userinfo = notification.userInfo else {
        return
    }

    guard
        let duration = (userinfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue,
        let endFrame = (userinfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
        let curveOption = userinfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
            return
    }

    UIView.animate(withDuration: duration, delay: 0, options: [.beginFromCurrentState, .init(rawValue: curveOption)], animations: {
        let edgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: endFrame.height, right: 0)
        self.scrollView.contentInset = edgeInsets
        self.scrollView.scrollIndicatorInsets = edgeInsets
    })
}

@objc private func keyboardWillHide(_ notification: Notification) {
    guard let userinfo = notification.userInfo else {
        return
    }

    guard
        let duration = (userinfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue,
        let curveOption = userinfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
            return
    }

    UIView.animate(withDuration: duration, delay: 0, options: [.beginFromCurrentState, .init(rawValue: curveOption)], animations: {
        let edgeInsets = UIEdgeInsets.zero
        self.scrollView.contentInset = edgeInsets
        self.scrollView.scrollIndicatorInsets = edgeInsets
    })
}

override func viewDidLoad() {
    super.viewDidLoad()

    // ...

    subscribeToKeyboardNotifications()
}

deinit {
    unsubscribeFromKeyboardNotifications()
}

private func subscribeToKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIWindow.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIWindow.keyboardWillHideNotification, object: nil)
}

private func unsubscribeFromKeyboardNotifications() {
    NotificationCenter.default.removeObserver(self, name: UIWindow.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.removeObserver(self, name: UIWindow.keyboardWillHideNotification, object: nil)
}

Objectif C

- (void)keyboardWillShow:(NSNotification *)sender
{
    CGFloat height = [[sender.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
    NSTimeInterval duration = [[sender.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    UIViewAnimationOptions curveOption = [[sender.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue] << 16;

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState|curveOption animations:^{
        UIEdgeInsets edgeInsets = UIEdgeInsetsMake(0, 0, height, 0);
        tableView.contentInset = edgeInsets;
        tableView.scrollIndicatorInsets = edgeInsets;
    } completion:nil];
}

- (void)keyboardWillHide:(NSNotification *)sender
{
    NSTimeInterval duration = [[sender.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    UIViewAnimationOptions curveOption = [[sender.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue] << 16;

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState|curveOption animations:^{
        UIEdgeInsets edgeInsets = UIEdgeInsetsZero;
        tableView.contentInset = edgeInsets;
        tableView.scrollIndicatorInsets = edgeInsets;
    } completion:nil];
}

Et dans - (void)viewDidLoad

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

Puis

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

0 votes

- (IBAction) est destiné aux méthodes qui doivent être liées aux composants de l'Interface Builder. Utilisation - (void). Vous ne spécifiez pas comment l'indexPath est obtenu ici.

0 votes

@quantumpotato oui, correct. -(void) textFieldDidBeginEditing:(UITextField *)textField { UITableViewCell *cell = (UITableViewCell *)[textField superview]; NSIndexPath *indexPath = [self.tableView indexPathForCell:cell]; [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; }

0 votes

Mais cette méthode ne fonctionne pas dans iOS6. Existe-t-il une alternative à cette méthode sous iOS6 ?

10voto

bmauter Points 523

Il s'agit d'une modification de la réponse de FunkyKat (un grand merci à FunkyKat !). Il serait probablement bénéfique de ne pas coder en dur UIEdgeInsetsZero pour la compatibilité future avec iOS.

Au lieu de cela, je demande la valeur actuelle de l'insert et je modifie la valeur du bas si nécessaire.

- (void)keyboardWillShow:(NSNotification *)sender {
    CGSize kbSize = [[[sender userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    NSTimeInterval duration = [[[sender userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

    CGFloat height = UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation]) ? kbSize.height : kbSize.width;
    if (isIOS8()) height = kbSize.height;

    [UIView animateWithDuration:duration animations:^{
        UIEdgeInsets edgeInsets = [[self tableView] contentInset];
        edgeInsets.bottom = height;
        [[self tableView] setContentInset:edgeInsets];
        edgeInsets = [[self tableView] scrollIndicatorInsets];
        edgeInsets.bottom = height;
        [[self tableView] setScrollIndicatorInsets:edgeInsets];
    }];
}

- (void)keyboardWillHide:(NSNotification *)sender {
    NSTimeInterval duration = [[[sender userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

    [UIView animateWithDuration:duration animations:^{
        UIEdgeInsets edgeInsets = [[self tableView] contentInset];
        edgeInsets.bottom = 0;
        [[self tableView] setContentInset:edgeInsets];
        edgeInsets = [[self tableView] scrollIndicatorInsets];
        edgeInsets.bottom = 0;
        [[self tableView] setScrollIndicatorInsets:edgeInsets];
    }];
}

0 votes

Comment récupérer l'indexPath en cas de réception d'une telle notification ?

0 votes

Je commente ma propre réponse car quelqu'un a édité ma réponse hier et je ne suis pas d'accord avec elle. Ils ont interverti les arguments de taille et de poids à la cinquième ligne. C'est incorrect. Avant iOS8, ma réponse originale était correcte. Après iOS8, Apple a changé ces valeurs de hauteur et de largeur pour les retourner en fonction de l'orientation (ou était-ce l'inverse ?), d'où l'appel if isIOS8(). Je laisse au lecteur le soin d'implémenter cette méthode.

8voto

Lauren Quantrell Points 1049

Pour le bien de toute autre personne rencontrant ce problème, je publie ici les méthodes nécessaires :

- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    NSString *identifier = [NSString stringWithFormat: @"%d:%d", [indexPath indexAtPosition: 0], [indexPath indexAtPosition:1]];

    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:identifier];

    if (cell == nil) {

        cell = [[[UITableViewCell alloc]  initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];

        UITextField *theTextField = [[UITextField alloc] initWithFrame:CGRectMake(180, 10, 130, 25)];

        theTextField.keyboardType = UIKeyboardTypeDefault;
        theTextField.returnKeyType = UIReturnKeyDone;
        theTextField.clearsOnBeginEditing = NO;
        theTextField.textAlignment = UITextAlignmentLeft;

        // (The tag by indexPath.row is the critical part to identifying the appropriate
        // row in textFieldDidBeginEditing and textFieldShouldEndEditing below:)

        theTextField.tag=indexPath.row;

        theTextField.delegate = self;

        theTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
        [theTextField setEnabled: YES];

        [cell addSubview:theTextField];

        [theTextField release];

    }

    return cell;
}

-(void) textFieldDidBeginEditing:(UITextField *)textField {

    int z = textField.tag;                                              

    if (z > 4) {

        // Only deal with the table row if the row index is 5 
        // or greater since the first five rows are already 
        // visible above the keyboard   

        // resize the UITableView to fit above the keyboard

        self.wordsTableView.frame = CGRectMake(0.0,44.0,320.0,200.0);       

        // adjust the contentInset

        wordsTableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 10);        

        // Scroll to the current text field

        [wordsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:z inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];

    }
}

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {

    // Determine which row is being edited

    int z = textField.tag;  

    if (z > 4) {

        // resize the UITableView to the original size

        self.wordsTableView.frame = CGRectMake(0.0,44.0,320.0,416.0);       

        // Undo the contentInset
        wordsTableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);         

    }

    return YES;

}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {

    // Dismisses the keyboard when the "Done" button is clicked

    [textField resignFirstResponder];

    return YES;                                 

}

0voto

GendoIkari Points 5949

Vous devez redimensionner le tableView lui-même pour qu'il ne passe pas sous le clavier.

-(void) textFieldDidBeginEditing:(UITextField *)textField {

// SUPPOSEDLY Scroll to the current text field
self.worldsTableView.frame = CGRectMake(//make the tableView smaller; to only be in the area above the keyboard);
CGRect textFieldRect = [textField frame];
[self.wordsTableView scrollRectToVisible:textFieldRect animated:YES];

}

Vous pouvez également utiliser une notification de clavier ; cela fonctionne légèrement mieux car vous disposez de plus d'informations et vous savez de manière plus cohérente quand le clavier s'affiche :

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

Et ensuite mettre en œuvre :

- (void)keyboardWillShow:(NSNotification *)notification {

}
- (void)keyboardWillHide:(NSNotification *)notification {

}

0 votes

J'ai redimensionné le tableau à 200 pixels pour qu'il tienne au-dessus du clavier, mais ce qui suit ne fait défiler le tableau que vers le HAUT jusqu'à la première ligne, et non vers le bas jusqu'à une ligne cachée : CGRect textFieldRect = [textField frame] ; [self.wordsTableView scrollRectToVisible:textFieldRect animated:YES] ;

0 votes

Pouvez-vous utiliser scrollToRowAtIndexPath: à la place ? Ce serait plus simple. Sinon, le problème est que textFieldRect est le cadre du textField, qui est son cadre par rapport à sa vue supérieure (qui est le contentView, ou tableViewCell). Vous devez convertir le rectangle dans le système de coordonnées de la tableView, au lieu de la tableViewCell. Utilisez convertRect:toView: pour ça.

0 votes

Je me suis dit que textFieldRect ne me donnait que la première ligne, puisqu'il s'agit d'un cadre à l'intérieur d'une vue, comme vous le suggérez, ce qui explique pourquoi il ne fait défiler que la ligne supérieure. Je ne sais pas comment utiliser convertRect:toView :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X