1613 votes

L'utilisation de l'Auto Mise en UITableView pour la dynamique des cellules mises en page et de la ligne de variable hauteurs

Comment utilisez-vous la Mise en page Automatique dans UITableViewCells dans un affichage tableau de laisser chaque contenu de la cellule et les sous-vues de déterminer la hauteur de ligne, tout en conservant le défilement régulier de la performance?

65voto

Adam Waite Points 2242

J'ai enveloppé @smileyborg de l'iOS7 solution dans une catégorie

Dans l'intérêt de séparer les préoccupations que j'ai décidé d'envelopper cette solution intelligente par @smileyborg en UICollectionViewCell+AutoLayoutDynamicHeightCalculation catégorie.

La catégorie rectifie les problèmes décrits dans @wildmonkey réponse (chargement d'une cellule à partir d'une plume et d' systemLayoutSizeFittingSize: de revenir CGRectZero)

Il ne prend pas en compte toute la mise en cache, mais est adapté à mes besoins maintenant. N'hésitez pas à copier-coller de hack.

UICollectionViewCell+AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell+AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Exemple d'utilisation:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

Heureusement, nous n'aurons pas à faire ce jazz dans iOS8, mais là c'est pour maintenant!

47voto

wildmonkey Points 412

La solution proposée par @smileyborg c'est presque parfait. Si vous avez une cellule personnalisé et vous souhaitez qu'une ou plusieurs UILabel avec dynamique hauteurs puis le systemLayoutSizeFittingSize méthode combinée avec la mise en page automatique activé renvoie une CGSizeZero, sauf si vous placez toutes vos cellules contraintes de la cellule à son contentView (comme suggéré par @TomSwift ici Comment redimensionner superview pour s'adapter à tous les sous-vues avec mise en page automatique?).

Pour ce faire, vous devez insérer le code suivant dans votre personnalisé UITableViewCell de mise en œuvre (merci à @Adrian).

-(void)awakeFromNib{
    [super awakeFromNib];
    for(NSLayoutConstraint *cellConstraint in self.constraints){
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint* contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Mélanger @smileyborg répondre avec ce il fonctionne.

26voto

Bob Spryn Points 6886

Un assez important gotcha je viens de croiser à poster une réponse.

@smileyborg la réponse est la plupart du temps correct. Toutefois, si vous avez un code dans l' layoutSubviews méthode de votre cellule personnalisé de classe, par exemple le réglage de l' preferredMaxLayoutWidth, alors il ne sera pas courir avec ce code:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

Il a confondu moi pendant un certain temps. Puis j'ai réalisé que c'est parce que ce ne sont que de déclenchement layoutSubviews sur l' contentView, pas de la cellule elle-même.

Mon code ressemble à ceci:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

Notez que si vous êtes à la création d'une nouvelle cellule, je suis sûr que vous n'avez pas besoin d'appeler setNeedsLayout comme il devrait déjà être réglé. Dans le cas où vous enregistrez une référence à une cellule, vous devriez probablement appeler. De toute façon, il ne faut pas mal quoi que ce soit.

Un autre conseil si vous utilisez de la cellule sous-classes où vous définissez les choses comme preferredMaxLayoutWidth. @Smileyborg mentionne, "votre vue de la table de la cellule n'a pas encore eu sa largeur fixe à la vue de la table de la largeur". C'est vrai, et la difficulté, si vous faites votre travail dans votre sous-classe et non dans la vue du contrôleur. Cependant, vous pouvez tout simplement mis de la cellule de trame à ce point à l'aide de la largeur de la table:

Par exemple, dans le calcul de la hauteur:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(Je cache cette cellule particulière pour les ré-utiliser, mais c'est sans importance).

19voto

Alex Points 636

Dans le cas où les gens sont toujours de la difficulté avec cela. J'ai envoyé un petit blog sur l'utilisation de la mise en page automatique avec UITableViews Tirant parti de la mise en page automatique Pour la Dynamique de la hauteur de Cellule ainsi que d'un composant open source pour aider à rendre cela plus abstraite et la plus facile à mettre en œuvre. https://github.com/Raizlabs/RZCellSizeManager

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