web-dev-qa-db-fra.com

Redimensionner UITableViewCell contenant UITextView lors de la frappe

J'ai un UITableViewController qui contient une cellule personnalisée. Chaque cellule a été créée à l'aide d'une nib et contient une seule UITextView non-scrollable. J'ai ajouté des contraintes à l'intérieur de chaque cellule afin que la cellule adapte sa hauteur au contenu de UITextView. Donc, au départ, mon contrôleur ressemble à ceci:

 initial state

Maintenant, je veux que lorsque l'utilisateur tape quelque chose dans une cellule, son contenu s'adapte automatiquement. Cette question a été posée à plusieurs reprises, voir en particulier this ou la deuxième réponse ici . J'ai donc écrit le délégué suivant dans mon code: 

- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
    return YES;
}

Toutefois, le comportement suivant est étrange: toutes les contraintes sont ignorées et la hauteur de toutes les cellules est réduite à la valeur minimale. Voir l'image ci-dessous:

 wrong behavior

Si je fais défiler la table en haut et en bas afin de forcer un nouvel appel de cellForRowAtIndexPath, je récupère les hauteurs correctes pour les cellules:

 correct behavior

Notez que j’ai mis pas à mettre en œuvre heightForRowAtIndexPath comme je l’attendais, autoLayout se chargera de cela.

Quelqu'un pourrait-il me dire ce que j'ai mal fait ou m'aider ici? Merci beaucoup !

13
vib

Voici une solution Swift qui fonctionne bien pour moi. Si vous utilisez la disposition automatique, vous devez attribuer une valeur à estimationRowHeight, puis renvoyer UITableViewAutomaticDimension pour la hauteur de ligne. Enfin, faites quelque chose de similaire à ci-dessous dans le délégué en vue texte.

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.estimatedRowHeight = 44.0
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

// MARK: UITextViewDelegate
func textViewDidChange(textView: UITextView) {

    // Calculate if the text view will change height, then only force 
    // the table to update if it does.  Also disable animations to
    // prevent "jankiness".

    let startHeight = textView.frame.size.height
    let calcHeight = textView.sizeThatFits(textView.frame.size).height  //iOS 8+ only

    if startHeight != calcHeight {

        UIView.setAnimationsEnabled(false) // Disable animations
        self.tableView.beginUpdates()
        self.tableView.endUpdates()

        // Might need to insert additional stuff here if scrolls
        // table in an unexpected way.  This scrolls to the bottom
        // of the table. (Though you might need something more
        // complicated if editing in the middle.)

        let scrollTo = self.tableView.contentSize.height - self.tableView.frame.size.height
        self.tableView.setContentOffset(CGPointMake(0, scrollTo), animated: false)

        UIView.setAnimationsEnabled(true)  // Re-enable animations.
}
36
atlwx

Ma solution est similaire à @atlwx mais un peu plus courte. Testé avec table statique. UIView.setAnimationsEnabled(false) est nécessaire pour empêcher le contenu de la cellule de "sauter" pendant que le tableau met à jour la hauteur de cette cellule

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.estimatedRowHeight = 44.0
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

func textViewDidChange(_ textView: UITextView) {
    UIView.setAnimationsEnabled(false)
    textView.sizeToFit()
    self.tableView.beginUpdates()
    self.tableView.endUpdates()
    UIView.setAnimationsEnabled(true)
}
6
Yaiba

L'exemple suivant fonctionne pour la hauteur de ligne dynamique lorsque l'utilisateur saisit du texte dans la cellule. Même si vous utilisez la disposition automatique, vous devez toujours implémenter la méthode heightForRowAtIndexPath. Pour que cet exemple fonctionne, les contraintes doivent être définies sur textView de sorte que si la hauteur de la cellule augmente, textView augmente également en hauteur. Cela peut être réalisé en ajoutant une contrainte supérieure et une limite inférieure de textView à la vue du contenu de la cellule. Mais ne définissez pas de contrainte de hauteur pour textView lui-même. Activez également le défilement de textView afin que la taille du contenu de textView soit mise à jour lorsque l'utilisateur saisit du texte. Nous utilisons ensuite cette taille de contenu pour calculer la nouvelle hauteur de ligne. Tant que la hauteur de la ligne est suffisamment longue pour étirer verticalement textView afin que sa taille soit égale ou supérieure à sa taille, la vue texte ne défilera pas, même si le défilement est activé et c'est ce dont vous avez besoin, je crois. 

Dans cet exemple, je n'ai qu'une seule ligne et je n'utilise qu'une seule variable pour garder une trace de la hauteur de la ligne. Mais lorsque nous avons plusieurs lignes, nous avons besoin d’une variable pour chaque ligne, sinon toutes les lignes auront la même hauteur. Un tableau de rowHeight qui correspond au tableau de source de données tableView peut être utilisé dans ce cas.

@interface ViewController ()

@property (nonatomic, assign)CGFloat rowHeight;;
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.rowHeight = 60;
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell1"];
    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return self.rowHeight;
}


#pragma mark - UITextViewDelegate

- (void)textViewDidChange:(UITextView *)textView {
    [self.tableView beginUpdates];
    CGFloat paddingForTextView = 40; //Padding varies depending on your cell design
    self.rowHeight = textView.contentSize.height + paddingForTextView;
    [self.tableView endUpdates];
}

@end
4
Jose Tomy Joseph

Si vous définissez TableView pour utiliser des dimensions automatiques (si vous travaillez dans une sous-classe UITableViewController, utilisez Swift 2.2 (les versions antérieures fonctionneraient probablement aussi), comme ceci:

    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.estimatedRowHeight = 50 // or something

Il vous suffit d'implémenter le délégué dans ce fichier, UITextViewDelegate, et d'ajouter la fonction ci-dessous, et cela devrait fonctionner. N'oubliez pas de définir le délégué de votre textView sur self (donc, peut-être après avoir retiré la cellule de la file d'attente, cell.myTextView.delegate = self)

func textViewDidChange(textView: UITextView) {
    self.tableView.beginUpdates()
    textView.frame = CGRectMake(textView.frame.minX, textView.frame.minY, textView.frame.width, textView.contentSize.height + 40)
    self.tableView.endUpdates()
}

Merci à "Jose Tomy Joseph" d’avoir inspiré (vraiment) cette réponse. 

3

J'ai implémenté une approche similaire en utilisant une UITextView, mais pour ce faire, j'ai dû implémenter heightForRowAtIndexPath.

#pragma mark - SizingCell

- (USNTextViewTableViewCell *)sizingCell
{
    if (!_sizingCell)
    {
        _sizingCell = [[USNTextViewTableViewCell alloc] initWithFrame:CGRectMake(0.0f,
                                                                                 0.0f,
                                                                                 self.tableView.frame.size.width,
                                                                                 0.0f)];
    }

    return _sizingCell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.sizingCell.textView.text = self.profileUpdate.bio;

    [self.sizingCell setNeedsUpdateConstraints];
    [self.sizingCell updateConstraintsIfNeeded];

    [self.sizingCell setNeedsLayout];
    [self.sizingCell layoutIfNeeded];

    CGSize cellSize = [self.sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return cellSize.height;
}

sizingCell est une instance de la cellule utilisée uniquement pour les calculs de dimensionnement.

Il est important de noter que vous devez attacher les bords supérieur et inférieur de UITextView aux bords supérieur et inférieur de UITableViewCells contentView afin que, lorsque UITableViewCell change de hauteur, UITextView change également de hauteur.

Pour la présentation de contrainte, j'utilise un PureLayout ( https://github.com/smileyborg/PureLayout ), de sorte que le code de présentation de contrainte suivant peut être inhabituel:

#pragma mark - Init

- (id)initWithStyle:(UITableViewCellStyle)style
    reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style
                reuseIdentifier:reuseIdentifier];

    if (self)
    {
        [self.contentView addSubview:self.textView];
    }

    return self;
}

#pragma mark - AutoLayout

- (void)updateConstraints
{
    [super updateConstraints];

    /*-------------*/

    [self.textView autoPinEdgeToSuperviewEdge:ALEdgeLeft
                                    withInset:10.0f];

    [self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop
                                    withInset:5.0f];

    [self.textView autoPinEdgeToSuperviewEdge:ALEdgeBottom
                                    withInset:5.0f];

    [self.textView autoSetDimension:ALDimensionWidth
                             toSize:200.0f];
}
1
williamb

Inspiré par les deux réponses précédentes, j'ai trouvé un moyen de résoudre mon problème. Je pense que le fait que j'avais une UITextView causait des problèmes avec autoLayout. J'ai ajouté les deux fonctions suivantes à mon code d'origine.

- (CGFloat)textViewHeightForAttributedText: (NSAttributedString*)text andWidth: (CGFloat)width {
    UITextView *calculationView = [[UITextView alloc] init];
    [calculationView setAttributedText:text];
    CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
    return size.height;
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UIFont *font = [UIFont systemFontOfSize:14.0];
    NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.sampleStrings[indexPath.row] attributes:attrsDictionary];
    return [self textViewHeightForAttributedText:attrString andWidth:CGRectGetWidth(self.tableView.bounds)-31]+20;
}

où dans la dernière ligne 31 est la somme de mes contraintes pour les côtés gauche et droit de la cellule et 20 est juste un jeu arbitraire.

J'ai trouvé cette solution en lisant ceci cette réponse très intéressante .

0
vib

L'astuce pour mettre à jour immédiatement la hauteur des cellules de la table, sans renoncer au clavier, consiste à exécuter l'extrait de code suivant à appeler dans l'événement textViewDidChange après avoir défini la taille de textView ou du contenu de la cellule:

[tableView beginUpdates];
[tableView endUpdates];

Cependant, cela ne suffira peut-être pas. Vous devez également vous assurer que la table a suffisamment d’élasticité pour conserver le même contentOffset. Vous obtenez cette élasticité en définissant le paramètre tableView contentInset bottom. Je suggère que cette valeur d'élasticité soit au moins égale à la distance maximale dont vous avez besoin du bas de la dernière cellule au bas de la vue tableau. Par exemple, cela pourrait être la hauteur du clavier. 

self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0);

Pour plus de détails et quelques fonctionnalités supplémentaires utiles à ce sujet, veuillez consulter le lien suivant: Redimensionner et déplacer UITableViewCell sans à-coups du clavier

0
Mig70

La solution que presque tout le monde a suggérée est la voie à suivre, je n’ajouterai qu’une amélioration mineure. En résumé:

  1. Il suffit de définir la hauteur estimée, je le fais via le storyboard:  Cell height in storyboard  TableView row height estimates

  2. Assurez-vous que les contraintes pour UITextView sont correctement définies dans la cellule.

  3. Dans la func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

J'appelle simplement: cell.myTextView.sizeToFit()

0
MNassar