使用自动布局,如何使UIImageView的大小dynamic取决于图像?

我想我的UIImageView增长或缩小取决于它显示的实际图像的大小。 但是我希望它能够保持垂直居中,距离超级观点的前沿10点。

但是,如果我设置这两个约束,它抱怨我没有设置足够的约束来满足自动布局。 对我来说,它似乎完美地描述了我想要的。 我错过了什么?

图像视图的内在大小已经取决于图像的大小。 你的假设(和约束是正确的)。

但是,如果您已经在界面构build器中设置了图像视图并且没有提供图像,那么布局系统(界面构build器)将不知道在编译时图像视图有多大。 您的布局不明确,因为您的图片视图可能有多种尺寸。 这是抛出错误。

一旦你设置了图像视图的图像属性,那么图像视图的内在大小是由图像的大小来定义的。 如果您在运行时设置视图,那么您可以完全按照Anna的说法进行操作,并在图像视图的属性检查器中为“占位符”固有尺寸提供界面生成器。 这告诉界面生成器,“现在使用这个大小,稍后我会给你一个真正的大小”。 运行时会忽略占位符约束。

你的其他select是将图像直接分配给界面生成器中的图像视图(但是我认为你的图像是dynamic的,所以这对你不起作用)。

这是基本问题: UIImageView与Auto Layout交互的唯一方式是通过其intrinsicContentSize属性。 该属性提供了图像本身的固有尺寸,仅此而已。 这创造了三种不同的解决scheme。

情况1:查看图像的大小

在第一种情况下,如果将UIImageView放置在外部Auto Layout约束仅影响其位置的上下文中,则视图的intrinsicContentSize将声明它想要成为其图像的大小,Auto Layout将自动调整视图的大小以等于它的形象大小。 这有时正是你想要的,然后生活是美好的!

情况2:完全约束视图大小,保持图像的纵横比

在其他时候,你处于另一种情况。 您完全知道图像视图需要的大小,但是您也希望它保留显示的图像的宽高比。 在这种情况下,可以使用“自动布局”来约束图像的大小,同时将旧的contentMode属性设置为图像视图中的.scaleAspectFit 。 该属性将导致图像视图重新缩放显示的图像,根据需要添加填充。 如果这是你想要的,生活是美好的!

情况3:部分受限视图大小,适应图像宽高比

但是,经常有些时候,你遇到了棘手的第三种情况。 您希望使用自动布局来部分确定视图的大小,同时允许图像的宽高比来确定其他部分。 例如,您可能希望使用“自动布局”约束来确定大小的一个维度(如垂直时间轴滚动中的宽度),同时依靠视图来告诉自动布局,它需要与图像的宽高比匹配的高度(所以可滚动的项目不会太短或太高)。

你不能configuration一个普通的图像视图来做到这一点,因为一个图像视图只传递其intrinsicContentSize 。 因此,如果将图像视图放置在“自动布局”约束一个维度的上下文中(例如,将其约束为一个较小的宽度),则图像视图不会告诉自动布局,它希望将其高度重新缩放。 它继续报告其内在的内容大小,与图像本身的未修改的高度。

为了configuration图像视图来告诉自动布局,它想要采用它包含的图像的宽高比,你需要添加一个新的约束。 此外,每当图像本身更新时,您都需要更新该约束。 你可以创build一个UIImageView子类,这样做的行为是自动的。 这是一个例子:

 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 } } } 

更多评论可在这个要点

如果你阅读UIImageView的文档,他们说:

设置图像属性不会改变UIImageView的大小。 调用sizeToFit来调整视图的大小以匹配图像。

这里是一个AspectFit UIImageView派生的实现,当只有一个维度受到限制时,与自动布局一起工作。 另一个将自动设置。 它将保持图像宽高比,并且不会在图像周围添加任何边距。

它调整了@algal想法的 Objective-C实现,具有以下不同之处:

  • expression式priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0其值为150不足以击败默认图像内容大小限制的优先级1000 。 所以方面约束的优先级也增加到了1000
  • 它删除\只在必要时重新添加纵横比约束。 请记住,这是繁重的操作,所以如果宽高比相同,则不需要这样做。 此外, image设置器可以被多次调用,所以在这里不要引起额外的布局计算是一个好主意。
  • 不知道为什么我们需要重写required public init?(coder aDecoder: NSCoder)public override init(frame:CGRect) ,所以这两个被取出。 它们不会被UIImageView覆盖,这就是为什么在设置图像之前添加方面约束是没有意义的。

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