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?
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).
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.
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!
À 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!
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
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
.
Voici une implémentation dérivée de AspectFit
UIImageView
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:
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
.image
setter 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.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