web-dev-qa-db-fra.com

Plusieurs UILabels dans une UITableViewCell à redimensionnement automatique

Dans cette application iOS 8 que je crée, j'ai une vue de table et j'ai besoin qu'elles soient auto-redimensionnantes. Je l'ai implémenté en utilisant la mise en page automatique et cela fonctionne. Presque. Voici à quoi cela ressemble maintenant.

enter image description here

Il y a 3 étiquettes dans une cellule. Etiquette principale qui contient le texte lorem ipsum. Sous-titre comportant la chaîne de chiffres (Il s’agit de deux étiquettes distinctes. Peut-être déroutant car elles ont la même couleur.) Ensuite, la troisième étiquette avec le petit texte noir.

La première étiquette s'est redimensionnée correctement sans problème et la deuxième étiquette se déplace de haut en bas en conséquence. Mais le problème est avec la troisième petite étiquette. Comme vous pouvez le constater, son redimensionnement ne s'adapte pas à tout le texte.

Maintenant, il se passe une chose étrange. Je tourne le paysage et voici.

enter image description here

Comme il y a de la place, l'étiquette affiche le texte entier, comme il est supposé. Bien. Puis je le retourne en portrait.

enter image description here

Maintenant, la petite étiquette s'est redimensionnée pour s'adapter à tout son texte mais elle déborde des limites de cellules. J'ai essayé d'agrandir la cellule mais cela n'a pas fonctionné. Puisqu'il s'agit de cellules auto-dimensionnées, je ne pense même pas que ce soit la bonne manière.

Je ne reçois aucune erreur ni même un avertissement concernant mes contraintes de mise en page automatique.

enter image description here

J'ai défini ces deux lignes de code dans la méthode viewDidLoad().

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

Quelqu'un peut-il s'il vous plaît me dire ce que je pourrais faire mal ici?

Comme il est difficile de répondre simplement en regardant les images et que je n'ai plus de code à publier à côté de l'extrait ci-dessus, j'ai téléchargé un projet Xcode exécutable illustrant le problème ici . (Il y a 2 cellules personnalisées. Fondamentalement, c'est la même cellule mais la hauteur est augmentée dans la seconde.)

Je bricolais avec les contraintes de mise en page automatique, mais cela ne semblait pas fonctionner correctement. Toute aide serait appréciée.

Je vous remercie.


METTRE À JOUR:

Avec l’aide de ce tutorial j’ai trouvé quelques indications utiles. Selon cette dernière, chaque sous-vue doit avoir des contraintes qui épinglent tous ses côtés et des contraintes de haut en bas facilitant la mise en page automatique pour calculer la hauteur de la cellule. Dans mon post original, j'avais des espaces verticaux entre chaque étiquette, donc je pense que c'est la raison pour laquelle la mise en page automatique ne peut pas calculer la hauteur appropriée.

Alors j'ai fait quelques changements.

  • J'ai réduit à 0 l'espace vertical entre les étiquettes et défini les contraintes d'espace vertical entre les étiquettes supérieures et intermédiaires et les étiquettes centrale et inférieure.
  • J'ai ajouté des contraintes de début, de début et de fin à l'étiquette supérieure.
  • Menant et traînant à l'étiquette du milieu.
  • Début, en bas, à la fin de l'étiquette en bas.

Maintenant, voici une autre partie étrange. Lorsque je l'ai lancé pour la première fois, le problème de recadrage de l'étiquette du bas est toujours là.

enter image description here

Mais si je fais pivoter l'appareil en mode paysage et le rend en mode portrait, toutes les cellules sont redimensionnées correctement pour correspondre aux deux étiquettes!

enter image description here

Je n'arrive toujours pas à comprendre pourquoi cela ne se produit pas au début. Le projet Xcode mis à jour est ici .

57
Isuru

Le problème ici concerne la propriété preferredMaxLayoutWidth des étiquettes multilignes. C'est la propriété qui indique à l'étiquette quand Word doit être renvoyé à la ligne. Elle doit être définie correctement pour que la variable intrinsicContentSize de chaque étiquette ait la hauteur correcte, ce qui correspond finalement à ce que la mise en page automatique utilisera pour déterminer la hauteur de la cellule.

Xcode 6 Interface Builder a introduit une nouvelle option permettant de définir cette propriété sur Automatique. Malheureusement, il existe certains bogues graves (à partir de Xcode 6.2/iOS 8.2) qui ne sont pas définis correctement/automatiquement lors du chargement d'une cellule à partir d'une plume ou d'un Storyboard.

Afin de contourner ce bogue, il faut que le paramètre preferredMaxLayoutWidth soit exactement égal à la largeur finale de l'étiquette une fois qu'il est affiché dans la vue tableau. Effectivement, nous souhaitons procéder comme suit avant de renvoyer la cellule à partir de tableView:cellForRowAtIndexPath::

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

La seule raison pour laquelle ajouter ce code seul ne fonctionne pas, c'est parce que, lorsque ces 3 lignes de code sont exécutées dans tableView:cellForRowAtIndexPath:, nous utilisons la largeur de chaque étiquette pour définir la preferredMaxLayoutWidth - cependant, si vous vérifiez la largeur des étiquettes à ce stade. À un moment donné, la largeur de l'étiquette est totalement différente de ce qu'elle sera une fois la cellule affichée et ses sous-vues définies.

Comment pouvons-nous obtenir des largeurs d'étiquettes exactes à ce stade, afin qu'elles reflètent leur largeur finale? Voici le code qui rend tout cela ensemble:

// Inside of tableView:cellForRowAtIndexPath:, after dequeueing the cell

cell.bounds = CGRect(x: 0, y: 0, width: CGRectGetWidth(tableView.bounds), height: 99999)
cell.contentView.bounds = cell.bounds
cell.layoutIfNeeded()

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

OK, alors qu'est-ce qu'on fait ici? Eh bien, vous remarquerez qu'il y a 3 nouvelles lignes de code ajoutées. Tout d'abord, nous devons définir la largeur de la cellule de cette vue tableau afin qu'elle corresponde à la largeur réelle de la vue tableau (cela suppose que la vue tableau a déjà été affichée et a sa largeur finale, ce qui devrait être le cas). En fait, nous ne faisons que corriger tôt la largeur de la cellule, car la vue sous forme de tableau finira par le faire.

Vous remarquerez également que nous utilisons 99999 pour la hauteur. Ça parles de quoi? Il s’agit d’une solution de contournement simple du problème traité en détail ici , où, si vos contraintes nécessitent plus d’espace vertical que la hauteur actuelle de contentView de la cellule, vous obtenez une exception de contrainte qui n’indique pas réellement de problème . La hauteur de la cellule ou de l'une de ses sous-vues importe peu à ce stade, car nous nous soucions uniquement d'obtenir les largeurs finales pour chaque étiquette.

Ensuite, nous nous assurons que la propriété contentView de la cellule a la même taille que celle que nous venons d'attribuer à la cellule elle-même, en définissant les limites de la propriété contentView de manière à ce qu'elles soient égales à celles de la cellule. Cela est nécessaire car toutes les contraintes de mise en page automatique que vous avez créées sont relatives à contentView. Par conséquent, la taille de contentView doit être correcte pour qu'elles puissent être résolues correctement. Le simple fait de définir manuellement la taille de la cellule ne pas redimensionne automatiquement le contentView pour qu'il corresponde.

Enfin, nous imposons un passage de présentation sur la cellule, ce qui permettra au moteur de présentation automatique de résoudre vos contraintes et de mettre à jour les cadres de toutes les sous-vues. Etant donné que la cellule et contentView ont désormais les mêmes largeurs qu'au moment de l'exécution dans la vue tableau, les largeurs d'étiquette seront également correctes, ce qui signifie que la valeur preferredMaxLayoutWidth définie pour chaque étiquette sera précise et entraînera son enroulement au bon moment , ce qui signifie bien sûr que les hauteurs des étiquettes seront définies correctement lorsque la cellule est utilisée dans la vue tableau!

Il s’agit certainement d’un bogue Apple dans UIKit que nous devons contourner pour le moment (veuillez donc faire rapports de bogue de fichier } avec Apple pour qu’ils priorisent un correctif!).

Remarque finale: cette solution de contournement rencontrera des problèmes si la largeur contentView de la cellule de votre vue tableau ne s'étend pas sur toute la largeur de la vue tableau, par exemple lorsqu'un index de section est affiché à droite. Dans ce cas, vous devrez vous assurer que vous en tenez compte manuellement lors de la définition de la largeur de la cellule. Vous devrez peut-être coder en dur ces valeurs, par exemple:

let cellWidth = CGRectGetWidth(tableView.bounds) - kTableViewSectionIndexWidth
cell.bounds = CGRect(x: 0, y: 0, width: cellWidth, height: 99999)
81
smileyborg

J'ai rencontré le même problème que vous et j'ai trouvé une solution simple pour le résoudre.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     // dequeue cell...

     // do autolayout staffs...or if the autolayout rule has been set in xib, do nothing

     [cell layoutIfNeeded];

     return cell;
}

Et l'auto-dimensionnement a bien fonctionné. Dans mon code, j'ai posé deux étiquettes à la verticale, les deux étant de hauteur dynamique. La hauteur de la cellule est correctement définie pour contenir les deux étiquettes. 

49
fogisland

En supposant que vous n'ayez aucune erreur dans vos contraintes comme le suggèrent d'autres personnes, ce problème semble provenir de l'utilisation d'un UILabel autorisant plusieurs lignes conjointement à un UITableViewCellAccessory. Lorsque iOS installe la cellule et détermine la hauteur, il ne tient pas compte de la modification de largeur en décalage qui se produit à cause de cet accessoire, et vous obtenez une troncature à un niveau inattendu. 

En supposant que vous vouliez que UILabel étende toute la largeur de la vue contenu, j'ai écrit une méthode qui résout ce problème pour toutes les tailles de police.

-(void)fixWidth:(UILabel *)label forCell:(UITableViewCell *)cell {
    float offset = 0;
    switch ([cell accessoryType]) {
        case UITableViewCellAccessoryCheckmark:
            offset = 39.0;
            break;
        case UITableViewCellAccessoryDetailButton:
            offset = 47.0;
            break;
        case UITableViewCellAccessoryDetailDisclosureButton:
            offset = 67.0;
            break;
        case UITableViewCellAccessoryDisclosureIndicator:
            offset = 33.0;
            break;
        case UITableViewCellAccessoryNone:
            offset = 0;
            break;
    }
    [label setPreferredMaxLayoutWidth:CGRectGetWidth([[self tableView]frame]) - offset - 8];
}

Il suffit de mettre cela dans votre cellForRowAtIndexPath

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    #Setup the cell
    ...
    // Fix layout with accessory view
    [self fixWidth:[cell label] forCell:cell];
    return cell;
}

Faites cela pour toutes les étiquettes qui vont avoir plusieurs lignes pour ajuster la largeur correctement et ensuite recalculer les hauteurs appropriées. Cela fonctionne également avec les tailles de police dynamiques.

Comme smileyborg l'avait mentionné, si vous n'utilisiez pas toute la largeur de contentView, vous pouvez référencer les contraintes et les soustraire de la largeur.

Edit: J'exécutais auparavant 'layoutIfNeeded' sur la cellule, mais cela créait des problèmes de performances et ne semblait pas nécessaire de toute façon. Le retirer ne m'a causé aucun problème.

6
user2898617

Vous avez deux problèmes ici.

1/Pour le moment, en utilisant Visual Format Language, les contraintes verticales de votre cellule peuvent être traduites comme suit:

Cell: "V:|-(10)-[nameLabel]-(67)-|"

Ensuite, vous définissez un deuxième groupe de contraintes:

Cell: "V:|-(10)-[nameLabel]-(8)-[pnrLabel]-(2)-[actionsLabel]"

Ces deux groupes de contraintes ne peuvent pas bien se mélanger et vont révéler leur ambiguïté avec votre deuxième problème.

2/Pour certaines raisons, actionsLabel est limité à une ligne lorsque vous lancez votre application. Ensuite, lorsque vous faites pivoter votre appareil en mode paysage, actionsLabel accepte de s'afficher avec deux lignes ou plus. Ensuite, lorsque vous faites pivoter votre appareil en mode portrait, actionsLabel continue d'afficher deux lignes ou plus. Toutefois, étant donné que actionsLabel ne fait pas vraiment partie des contraintes de hauteur de votre cellule, elle chevauche les limites de votre cellule.

Si vous voulez résoudre tous ces problèmes, je vous recommande tout d’abord de reconstruire votre fichier xib à partir de zéro. Cela corrigera votre comportement étrange actionsLabel (deux lignes ou plus uniquement lorsque vous faites pivoter votre appareil).

Ensuite, vous devrez définir vos contraintes comme ceci:

Cell: "V:|-(10)-[nameLabel(>=21)]-(8)-[pnrLabel(>=21)]-(2)-[actionsLabel(>=21)]-(10)-|"

Bien entendu, vous pouvez définir d'autres contraintes de hauteur minimale pour vos étiquettes que (>=21). De la même manière, votre marge inférieure peut être définie sur une autre valeur que -(10)-.

Addenda

Afin de répondre à votre question, j'ai créé un projet simple avec le modèle de contraintes précédent dans mon fichier .xib. L'image suivante peut vous aider à créer vos propres contraintes.

enter image description here

4
Imanou Petit

J'ai essayé la solution très simple et élégante de "fogisland" - et cela n'a pas fonctionné. Heureusement, j'ai découvert qu'une ligne supplémentaire permet de travailler dans toutes les directions. Indiquez simplement au système que vous ne proposez pas seulement une nouvelle présentation (layoutIfNeeded), vous le demandez explicitement (setNeedLayout).

    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    return cell
1
Hardy_Germany