使用自动布局,如何根据图像使UIImageView的大小动态?

Dou*_*ith 50 objective-c uiimageview ios autolayout nslayoutconstraint

我希望我UIImageView的增长或缩小取决于它显示的实际图像的大小.但我希望它保持垂直居中,距离超视距的前沿10分钟.

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

小智 68

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

但是,如果您在界面构建器中设置了图像视图并且没有为其提供图像,则布局系统(界面构建器)将不知道图像视图在编译时应该有多大.您的布局不明确,因为您的图片视图可能有多种尺寸.这就是抛出错误的原因.

设置图像视图的图像属性后,图像视图的内在大小由图像的大小定义.如果您在运行时设置视图,那么您可以完全按照Anna提到的内容进行操作,并在图像视图的属性检查器中为界面构建器提供"占位符"内在大小.这告诉界面构建器,"现在使用这个大小,我稍后会给你一个真实的大小".占位符约束在运行时被忽略.

您的另一个选择是直接将图像分配给界面构建器中的图像视图(但我假设您的图像是动态的,因此这对您不起作用).


alg*_*gal 38

这是一个基本问题:UIImageView与Auto Layout交互的唯一方法是通过其intrinsicContentSize属性.该属性提供了图像本身的内在大小,仅此而已.这创造了三种情况,具有不同的解决方案

案例1:查看图像的大小

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

案例2:完全约束视图大小,保留图像的宽高比

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

案例3:部分约束视图大小,适应图像宽高比

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

您无法配置普通图像视图来执行此操作,因为图像视图仅与其通信intrinsicContentSize.因此,如果将图像视图放在自动布局约束一个维度的上下文中(例如,将其约束为较短的宽度),则图像视图不会告诉自动布局它希望其高度重新缩放.它继续报告其内在的内容大小,图像本身的未修改高度.

为了配置图像视图以告诉自动布局它想要采用它包含的图像的纵横比,您需要为该效果添加新约束.此外,每当更新图像本身时,您都需要更新该约束.您可以构建一个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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这个要点可以提供更多评论

  • 好吧,我实际上最终使用了 `c.priority = UILayoutPriority.init(999.0)` 因为这个答案中的低优先级不会这样做。也许这取决于你对图像视图的其他限制,但我只有四个;单元格内容视图的顶部/底部/尾部/前导。 (2认同)
  • 我花了一天的时间试图理解为什么我不能配置案例 3 !@algal 你提供了我遗漏的解释!!非常感谢 ! (2认同)
  • 我需要在初始化后写 c.isActive = true 并将优先级设置为 1000 以使其工作。但它现在有效,所以谢谢你! (2认同)

xir*_*rix 11

对于@algal案例3,我所要做的就是

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这会缩放intrinsicContentSize的高度分量.没有图像时返回(-1.0,-1.0),因为这是超类中的默认行为.

此外,我不需要为UITableViewAutomaticDimension包含图像视图的单元格设置表格,并且单元格仍然自动调整大小.图像视图的内容模式是Aspect Fit.

  • 当在StackView中使用时,我遇到一种情况,即在视图范围更改时不会重新计算inrinsicContentSize,因此我不得不从layoutSubviews()调用invalidateIntrinsicContentSize():https://gist.github.com / marcc-orange / e309d86275e301466d1eecc8e400ad00 (2认同)

Ale*_*mov 5

这是一个AspectFit UIImageView派生的实现,当只有一个维度受到约束时,它可以与自动布局一起使用。另一项将自动设置。它将保持图像的长宽比,并且不会在图像周围添加任何边距。

它是@algal idea的 Objective-C 实现的调整,具有以下差异:

  • priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0计算结果为 的表达式150 不足以超越1000默认图像内容大小约束的优先级。1000因此方面约束优先级也增加了。
  • 仅在必要时才删除\重新添加宽高比约束。请记住,这是一项繁重的操作,因此如果宽高比相同,则无需这样做。此外,imagesetter 可以被多次调用,因此最好不要在此处引起额外的布局计算。
  • 不知道为什么我们需要覆盖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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)