web-dev-qa-db-fra.com

Avec la mise en page automatique, comment rendre dynamique la taille d'un UIImageView en fonction de l'image?

Je veux que mon UIImageView grossisse ou se rétrécisse en fonction de la taille de l'image affichée. Mais je veux qu'il reste verticalement centré et à 10 points du bord d'attaque du superview.

Cependant, si je définis ces deux contraintes, il se plaint de ne pas avoir configuré suffisamment de contraintes pour satisfaire la mise en page automatique. Pour moi, cela semble parfaitement décrire ce que je veux. Qu'est-ce que je rate?

46
Doug Smith

La taille intrinsèque de la vue d'image dépend déjà de la taille de l'image. Vos hypothèses (et les contraintes sont correctes).

Toutefois, si vous avez configuré votre vue d'image dans le constructeur d'interface et ne lui avez pas fourni d'image, le système de disposition (constructeur d'interface) ne saura pas quelle est la taille de votre vue d'image au moment de la compilation. Votre mise en page est ambiguë car la vue de votre image peut avoir plusieurs tailles. C'est ce qui jette les erreurs.

Une fois que vous avez défini la propriété image de votre vue image, la taille intrinsèque de la vue image est définie par la taille de l'image. Si vous définissez la vue au moment de l'exécution, vous pouvez alors appliquer exactement ce que Anna a mentionné et fournir au constructeur d'interface une taille intrinsèque "d'espace réservé" dans l'inspecteur de propriétés de la vue d'image. Cela dit au constructeur d’interface, "utilisez cette taille pour le moment, je vous donnerai une taille réelle plus tard". Les contraintes d'espace réservé sont ignorées au moment de l'exécution.

Votre autre option consiste à affecter directement l'image à la vue d'image dans le générateur d'interface (mais je suppose que vos images sont dynamiques, donc cela ne fonctionnera pas pour vous).

66
John Grant

Voici le problème essentiel: la seule façon dont UIImageView interagisse avec la disposition automatique est via sa propriété intrinsicContentSize. Cette propriété fournit la taille intrinsèque de l'image elle-même et rien de plus. Cela crée trois situations, avec des solutions différentes.

cas 1: vue avec la taille de l'image

Dans le premier cas, si vous placez un UIImageView dans un contexte où des contraintes externes de mise en forme automatique affectent uniquement sa position, la vue intrinsicContentSize de la vue déclarera vouloir être à la taille de son image et La disposition automatique redimensionnera automatiquement la vue afin qu'elle corresponde à la taille de son image. C'est parfois exactement ce que vous voulez, et alors la vie est belle!

cas 2: taille de la vue totalement contrainte, préservant les proportions de l'image

À d'autres moments, vous êtes dans un cas différent. Vous savez exactement la taille que vous souhaitez que l'image prenne, mais vous souhaitez également que le rapport hauteur/largeur de l'image affichée soit préservé. Dans ce cas, vous pouvez utiliser la disposition automatique pour contraindre la taille de l'image, tout en définissant l'ancienne propriété contentMode sur .scaleAspectFit sur la vue de l'image. Grâce à cette propriété, la vue de l'image redimensionnera l'image affichée, en ajoutant un remplissage si nécessaire. Et si c'est ce que vous voulez, la vie est belle!

cas 3: taille de la vue partiellement contrainte, adaptation au format de l'image

Mais souvent, vous êtes dans le troisième cas délicat. Vous souhaitez utiliser la disposition automatique pour déterminer partiellement la taille de la vue, tout en permettant au rapport de format de l'image de déterminer l'autre partie. Par exemple, vous pouvez utiliser les contraintes de disposition automatique pour déterminer une dimension de la taille (comme la largeur, dans un défilement vertical de la timeline), tout en vous fiant à la vue pour indiquer à Auto Layout qu’elle souhaite une hauteur correspondant au rapport d’aspect de l’image ( les éléments à faire défiler ne sont donc ni trop courts ni trop grands.

Vous ne pouvez pas configurer une vue d'image ordinaire pour cela, car une vue d'image ne communique que son intrinsicContentSize. Ainsi, si vous placez une vue d'image dans un contexte où la mise en page automatique contraint une dimension (par exemple, en la limitant à une largeur réduite), la vue d'image n'indiquera pas à Auto Layout qu'il souhaite que sa hauteur soit redimensionnée en tandem. Il continue de signaler la taille de son contenu intrinsèque, avec la hauteur non modifiée de l'image elle-même.

Afin de configurer la vue d'image pour indiquer à Auto Layout qu'elle souhaite prendre en compte le rapport de format de l'image qu'elle contient, vous devez ajouter une nouvelle contrainte à cet effet. De plus, vous devrez mettre à jour cette contrainte chaque fois que l'image elle-même est mise à jour. Vous pouvez construire une sous-classe UIImageView qui le fait, le comportement est donc automatique. Voici un exemple:

public class ScaleAspectFitImageView : UIImageView {
  /// constraint to maintain same aspect ratio as the image
  private var aspectRatioConstraint:NSLayoutConstraint? = nil

  required public init?(coder aDecoder: NSCoder) {
    super.init(coder:aDecoder)
    self.setup()
  }

  public override init(frame:CGRect) {
    super.init(frame:frame)
    self.setup()
  }

  public override init(image: UIImage!) {
    super.init(image:image)
    self.setup()
  }

  public override init(image: UIImage!, highlightedImage: UIImage?) {
    super.init(image:image,highlightedImage:highlightedImage)
    self.setup()
  }

  override public var image: UIImage? {
    didSet {
      self.updateAspectRatioConstraint()
    }
  }

  private func setup() {
    self.contentMode = .scaleAspectFit
    self.updateAspectRatioConstraint()
  }

  /// Removes any pre-existing aspect ratio constraint, and adds a new one based on the current image
  private func updateAspectRatioConstraint() {
    // remove any existing aspect ratio constraint
    if let c = self.aspectRatioConstraint {
      self.removeConstraint(c)
    }
    self.aspectRatioConstraint = nil

    if let imageSize = image?.size, imageSize.height != 0
    {
      let aspectRatio = imageSize.width / imageSize.height
      let c = NSLayoutConstraint(item: self, attribute: .width,
                                 relatedBy: .equal,
                                 toItem: self, attribute: .height,
                                 multiplier: aspectRatio, constant: 0)
      // a priority above fitting size level and below low
      c.priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0
      self.addConstraint(c)
      self.aspectRatioConstraint = c
    }
  }
}

Plus de commentaires sont disponibles sur this Gist

36
algal

Pour le cas 3 de @algal, tout ce que je devais faire était de

class ScaledHeightImageView: UIImageView {

    override var intrinsicContentSize: CGSize {

        if let myImage = self.image {
            let myImageWidth = myImage.size.width
            let myImageHeight = myImage.size.height
            let myViewWidth = self.frame.size.width

            let ratio = myViewWidth/myImageWidth
            let scaledHeight = myImageHeight * ratio

            return CGSize(width: myViewWidth, height: scaledHeight)
        }
        return CGSize(width: -1.0, height: -1.0)
    }
}

Cela met à l'échelle le composant hauteur de la propriété intrinsicContentSize. (-1.0, -1.0) est renvoyé en l'absence d'image car il s'agit du comportement par défaut de la superclasse.

De plus, je n'avais pas besoin de définir UITableViewAutomaticDimension pour le tableau avec la cellule contenant la vue, et la taille de la cellule reste automatiquement. Le mode de contenu de la vue d'image est Aspect Fit.

10
xirix

Voici une implémentation dérivée de AspectFitUIImageView qui fonctionne avec la mise en page automatique quand une seule dimension est contrainte. L'autre sera réglé automatiquement. Il conservera le rapport hauteur/largeur de l’image et n’ajoutera aucune marge autour de l’image.

Sa mise en œuvre Objective-C de @ algal idea avec les différences suivantes:

  • L'expression priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0 qui se traduit par 150 Ne suffit pas pour battre la priorité de 1000 De la contrainte de taille de contenu d'image par défaut. Ainsi, la priorité des contraintes d’aspect a également été augmentée à 1000.
  • il supprime\re-ajoute la contrainte de rapport d'aspect uniquement si nécessaire. Gardez à l'esprit qu'il s'agit d'une opération lourde, il n'est donc pas nécessaire de le faire si le rapport d'aspect est le même. De plus, imagesetter peut être appelé plusieurs fois, c'est donc une bonne idée de ne pas générer de calculs de disposition supplémentaires ici.
  • vous ne savez pas pourquoi nous devons remplacer required public init?(coder aDecoder: NSCoder) et public override init(frame:CGRect), de sorte que ces deux éléments sont supprimés. Ils ne sont pas écrasés par UIImageView, c’est pourquoi il est inutile d’ajouter la contrainte d’aspect tant qu’une image n’est pas définie.

AspectKeepUIImageView.h:

NS_ASSUME_NONNULL_BEGIN

@interface AspectKeepUIImageView : UIImageView

- (instancetype)initWithImage:(nullable UIImage *)image;
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage;

@end

NS_ASSUME_NONNULL_END

AspectKeepUIImageView.m:

#import "AspectKeepUIImageView.h"

@implementation AspectKeepUIImageView
{
    NSLayoutConstraint *_aspectContraint;
}

- (instancetype)initWithImage:(nullable UIImage *)image
{
    self = [super initWithImage:image];

    [self initInternal];

    return self;
}

- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage
{
    self = [super initWithImage:image highlightedImage:highlightedImage];

    [self initInternal];

    return self;
}

- (void)initInternal
{
    self.contentMode = UIViewContentModeScaleAspectFit;
    [self updateAspectConstraint];
}

- (void)setImage:(UIImage *)image
{
    [super setImage:image];
    [self updateAspectConstraint];
}

- (void)updateAspectConstraint
{
    CGSize imageSize = self.image.size;
    CGFloat aspectRatio = imageSize.height > 0.0f
        ? imageSize.width / imageSize.height
        : 0.0f;
    if (_aspectContraint.multiplier != aspectRatio)
    {
        [self removeConstraint:_aspectContraint];

        _aspectContraint =
        [NSLayoutConstraint constraintWithItem:self
                                     attribute:NSLayoutAttributeWidth
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:self
                                     attribute:NSLayoutAttributeHeight
                                    multiplier:aspectRatio
                                      constant:0.f];

        _aspectContraint.priority = UILayoutPriorityRequired;

        [self addConstraint:_aspectContraint];
    }
}

@end
2