web-dev-qa-db-fra.com

Mise en page automatique: obtenir la hauteur UIImageView pour calculer la hauteur de cellule correctement

Jusqu'à présent, je n'utilisais que des étiquettes de texte dans des cellules qui devraient se redimensionner elles-mêmes. Je mets généralement des contraintes sur toutes les arêtes (vue du contenu ou voisins) et ajoute les lignes suivantes à ma viewDidLoad():

// 190.0 is the actual height of my custom cell (see next image) in the storyboard containing a label and a randomly sized UIImageView
tableView.estimatedRowHeight = 190.0
tableView.rowHeight = UITableViewAutomaticDimension

Maintenant, lorsque j'essaie d'inclure des images, je suis confronté à plusieurs problèmes. Au cours de certaines recherches, j'ai trouvé plusieurs approches écrites dans Objective-C, mais alors que je ne savais pas laquelle de celles-ci résolvait réellement mon problème central, je suis toujours aux prises avec un code Objective-C complexe. Donc, pour résumer: Mon objectif final est d’obtenir un tableau avec des cellules correctement dimensionnées (hauteurs correctes), chacune contenant un titre et une image qui correspond à la largeur de l’écran. Ces images peuvent être de différents formats et de différentes tailles. Pour simplifier certains des défis, j'ai préparé un nouveau projet, plus simple:

  1. Tout d'abord, j'ai créé une classe UITableViewController et une CustomCell avec deux IBOutlets title: String et postedImage: UIImage (UIImage de taille aléatoire dans le storyboard) pour configurer les cellules. J'ai défini des contraintes aux bords et les unes aux autres.

  1. Une fois construites, deux cellules contenant la même image sont configurées, mais elles sont extraites de deux fichiers individuels de tailles et de résolutions différentes (900 x 171 par rapport à une taille réduite de 450 x 86). Le mode d'affichage UIImage est réglé sur "Aspect Fit", mais comme je ne pouvais pas le faire fonctionner comme je le voulais jusqu'à présent, je ne suis pas sûr que ce soit le bon réglage. Alors que je m'attendais à ce que la hauteur de la cellule soit calculée sur la base de UIImage redimensionnée, y compris les contraintes que j'avais définies, elle semble être basée sur la hauteur initiale des fichiers image (donc 171 et 86). Il ne s'adapte pas réellement à la hauteur de vue réelle de UIImage (environ 70, je suppose).

Both cell heights are based on the initial image file height and not on the actual UIImage height

Dans un projet un peu plus complexe, je souhaite que différentes images s’adaptent automatiquement à la largeur de l’écran, qu’il s’agisse de portrait ou de paysage. Si leur largeur est inférieure à celle de l’écran, elles doivent être redimensionnées. Comme dans le projet ci-dessus, chaque hauteur de cellule doit correspondre à son contenu individuel, tel que défini par les contraintes du storyboard. Quoi qu’il en soit, cela commence par le problème évoqué ci-dessus: comment puis-je laisser ces deux cellules avoir la même hauteur, avec une hauteur correcte, épousant ainsi son contenu comme défini dans le storyboard?

Merci mille fois pour toute aide!

34
Alexei S.

Je pense donc que le problème sous-jacent est un problème de poulet et d’œufs.

Pour "ajuster" l'image à la UIImageView, le système a besoin d'une taille pour la vue. Cependant, nous aimerions que la hauteur de la vue soit déterminée après l'aspect correspondant à l'image avec une largeur connue pour la vue. .

Une solution de contournement consiste à calculer nous-mêmes le rapport de format de l'image et à le définir comme une contrainte sur la variable UIImageView lorsque nous définissons l'image. Ainsi, le système de mise en page automatique dispose de suffisamment d’informations pour redimensionner la vue (une largeur et un rapport hauteur/largeur).

J'ai donc changé votre classe de cellules personnalisée en:

class CustomCell: UITableViewCell {

    @IBOutlet weak var imageTitle: UILabel!

    @IBOutlet weak var postedImageView: UIImageView!

    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                postedImageView.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                postedImageView.addConstraint(aspectConstraint!)
            }
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        aspectConstraint = nil
    }

    func setPostedImage(image : UIImage) {

        let aspect = image.size.width / image.size.height

        aspectConstraint = NSLayoutConstraint(item: postedImageView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: postedImageView, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)

        postedImageView.image = image
    }

}

Et ensuite, dans le délégué, faites ceci au lieu de définir l'image directement:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell

        cell.imageTitle.text = titles[indexPath.row]

        let image = images[titles[indexPath.row]]!
        cell.setPostedImage(image)

        return cell
}

Et vous aurez:

enter image description here

J'espère que cela t'aides!

70
Mike Pollard

J'ai traduit la solution de Pollard en version objc quelque chose comme ceci. 
J'ai également reçu des avertissements du type "Impossible de satisfaire simultanément les contraintes". Ma solution est de définir la priorité de contrainte d’espace vertical sur 999 plutôt que sur 1000. 

@interface PostImageViewCell()

@property (nonatomic, strong) NSLayoutConstraint *aspectContraint;
@end

@implementation PostImageViewCell

- (void) setAspectContraint:(NSLayoutConstraint *)aspectContraint {

    if (_aspectContraint != nil) {
        [self.postImage removeConstraint:_aspectContraint];
    }
    if (aspectContraint != nil) {
        [self.postImage addConstraint:aspectContraint];
    }
}

- (void) prepareForReuse {
    [super prepareForReuse];
    self.aspectContraint = nil;
}

- (void)setPicture:(UIImage *)image {

    CGFloat aspect = image.size.width / image.size.height;
    self.aspectContraint = [NSLayoutConstraint constraintWithItem:self.postImage
                                                        attribute:NSLayoutAttributeWidth
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self.postImage
                                                        attribute:NSLayoutAttributeHeight
                                                       multiplier:aspect
                                                         constant:0.0];
    [self.postImage setImage:image];
}
@end
7
허광호

Travailler dans Swift 3 + iOS 8  

Utilisation d’AutoLayout et d’objets UITableViewCell de taille variable -

Pour une image, définissez les contraintes d’espace haut, bas, avant et arrière sur 0.

Code à ajouter à viewDidLoad ()

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200

Définir le paramètre heightForRowAtIndexPath () de UITableViewDelegate

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let image = UIImage(named: list_images[indexPath.row])
        let aspect = (image?.size.width)! / (image?.size.height)!
        let cellHeight = (tableView.frame.width / aspect) + <<Height of image title label>>
        return cellHeight
    }
}
1
c_sharma

Au lieu d'avoir la méthode setPostedImage personnalisée, il est plus pratique de mettre à jour la contrainte d'aspect dans updateConstraints. De cette façon, vous pouvez modifier l'image de UIImageView directement sans méthode d'assistance supplémentaire:

static NSLayoutConstraint *constraintWithMultiplier(NSLayoutConstraint *constrain, CGFloat multiplier) {
    return [NSLayoutConstraint constraintWithItem:constrain.firstItem
                                        attribute:constrain.firstAttribute
                                        relatedBy:constrain.relation
                                           toItem:constrain.secondItem
                                        attribute:constrain.secondAttribute
                                       multiplier:multiplier
                                         constant:constrain.constant];
}

-(void)viewDidLoad
{
    UIView *contentView = self.contentView;

    _imageView = [[UIImageView alloc] init];
    _imageView.translatesAutoresizingMaskIntoConstraints = NO;
    _imageView.contentMode = UIViewContentModeScaleAspectFit;
    [contentView addSubview:_imageView];

    _imageAspectConstraint = [_imageView.heightAnchor constraintEqualToAnchor:_imageView.widthAnchor multiplier:1.0f];
    NSArray <NSLayoutConstraint *> *constraints = @[
            [_imageView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:0],
            [_imageView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:0],
            [_imageView.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:0],
            [_imageView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor constant:0],
    ];
    [NSLayoutConstraint activateConstraints:constraints];
}

-(void)updateConstraints
{
    const CGSize size = _imageView.image.size;
    const CGFloat multiplier = size.width / size.height;
    _imageAspectConstraint.active = NO;
    _imageAspectConstraint = constraintWithMultiplier(_imageAspectConstraint, multiplier);
    _imageAspectConstraint.active = YES;
    [super updateConstraints];
}
0
wonder.mice

Swift 4

Calculez la hauteur de l'image à l'aide de UIImageextension.

Vous pouvez utiliser extension sur UIImage pour obtenir la hauteur en fonction de AspectRatio de l'image.

extension UIImage {
    var cropRatio: CGFloat {
        let widthRatio = CGFloat(self.size.width / self.size.height)
        return widthRatio
    }
}

Donc, dans votre méthode override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat.

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            let currentImage = dataSource[indexPath.row].image
            let imageCrop = currentImage.cropRatio
            var _height = tableView.frame.width / imageCrop
            return _height
}
0
Rashesh Bosamiya