繁体   English   中英

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

[英]With Auto Layout, how do I make a UIImageView's size dynamic depending on the image?

我希望我的UIImageView根据它显示的实际图像的大小来增长或缩小。 但我希望它保持垂直居中,距离超级视图的前缘 10pts。

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

图像视图的固有大小已经取决于图像的大小。 您的假设(和约束是正确的)。

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

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

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

这是基本问题: UIImageView与自动布局交互的唯一方式是通过其intrinsicContentSize属性。 该属性提供图像本身的内在大小,仅此而已。 这会产生三种情况,具有不同的解决方案。

案例1:查看图像的大小

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

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

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

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

更多评论可在 此要点中找到

对于@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)
    }
}

这将缩放内在内容大小的高度组件。 (-1.0, -1.0) 在没有图像时返回,因为这是超类中的默认行为。

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

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

它调整了@algal 想法的Objective-C 实现,具有以下差异:

  • 表达式priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0评估为150不足以击败默认图像内容大小约束的优先级1000 因此方面约束优先级也增加到1000
  • 它仅在必要时删除\\重新添加纵横比约束。 请记住,这是一项繁重的操作,因此如果纵横比相同,则无需这样做。 此外, image设置器可能会被多次调用,因此最好不要在此处进行额外的布局计算。
  • 不知道为什么我们需要覆盖required public init?(coder aDecoder: NSCoder)public override init(frame:CGRect) ,所以把这两个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

我使用了@algal 的优秀答案(案例#3)并针对我的用例进行了改进。

我希望能够设置最小和最大比率约束 因此,如果我将最小比率约束设置为 1.0 并设置高于宽度的图片,则大小应为正方形。 在这些情况下,它也应该填充UIImageView

这甚至在 Interface Builder 中也有效。 只需添加一个UIImageView ,在身份检查器中将RatioBasedImageView设置为自定义类,并在属性检查器中设置最大和最小纵横比。

这是我的代码。

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

    // This makes it use the correct size in Interface Builder
    public override func prepareForInterfaceBuilder() {
        invalidateIntrinsicContentSize()
    }

    @IBInspectable
    var maxAspectRatio: CGFloat = 999 {
        didSet {
            updateAspectRatioConstraint()
        }
    }

    @IBInspectable
    var minAspectRatio: CGFloat = 0 {
        didSet {
            updateAspectRatioConstraint()
        }
    }


    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.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 constraint = self.aspectRatioConstraint {
            self.removeConstraint(constraint)
        }
        self.aspectRatioConstraint = nil

        if let imageSize = image?.size, imageSize.height != 0 {
            var aspectRatio = imageSize.width / imageSize.height            
            aspectRatio = max(minAspectRatio, aspectRatio)
            aspectRatio = min(maxAspectRatio, aspectRatio)

            let constraint = NSLayoutConstraint(item: self, attribute: .width,
                                       relatedBy: .equal,
                                       toItem: self, attribute: .height,
                                       multiplier: aspectRatio, constant: 0)
            constraint.priority = .required
            self.addConstraint(constraint)
            self.aspectRatioConstraint = constraint
        }
    }
}

我通常不添加值为0priority小于UIImageViewcontentCompressionResistancePriority图像高度和宽度约束,默认情况下为750 这两个约束只有在没有图像时才会激活,并且会在运行时和编译时处理不明确的约束警告。

在 IB 中添加这些非常简单。 以编程方式,它应该如下所示。

let noImageWidthConstraint = imageview.widthAnchor.constraint(equalToConstant: 0)
noImageWidthConstraint.priority = UILayoutPriority(rawValue: 100)
noImageWidthConstraint.isActive = true

let noImageHeightConstraint = imageview.heightAnchor.constraint(equalToConstant: 0)
noImageHeightConstraint.priority = UILayoutPriority(rawValue: 100)
noImageHeightConstraint.isActive = true

如果有人来这里想要 Xamarin 的简单复制+粘贴,@algal 的代码(带有一些 @alexander 的更改)转换为 .Net

此外,我已将其更改为基于宽度 + 纵横比限制高度,因为这适合我的所有用例。

/// <summary>
///  UIImage view that will set the height correct based on the current
///  width and the aspect ratio of the image.
///  https://stackoverflow.com/a/42654375/9829321
/// </summary>
public class UIAspectFitImageView : UIImageView
{
    private NSLayoutConstraint _heightConstraint;

    public override UIImage Image
    {
        get => base.Image;
        set
        {
            base.Image = value;
            UpdateAspectRatioConstraint();
        }
    }

    public UIAspectFitImageView()
        => Setup();

    public UIAspectFitImageView(UIImage image) : base(image)
        => Setup();

    public UIAspectFitImageView(NSCoder coder) : base(coder)
        => Setup();

    public UIAspectFitImageView(CGRect frame) : base(frame)
        => Setup();

    public UIAspectFitImageView(UIImage image, UIImage highlightedImage) : base(image, highlightedImage)
        => Setup();

    private void Setup()
    {
        ContentMode = UIViewContentMode.ScaleAspectFit;
        UpdateAspectRatioConstraint();
    }

    private void UpdateAspectRatioConstraint()
    {
        if (Image != null && Image.Size.Width != 0)
        {
            var aspectRatio = Image.Size.Height / Image.Size.Width;

            if (_heightConstraint?.Multiplier != aspectRatio)
            {
                if (_heightConstraint != null)
                {
                    RemoveConstraint(_heightConstraint);
                    _heightConstraint.Dispose();
                    _heightConstraint = null;
                }

                _heightConstraint = HeightAnchor.ConstraintEqualTo(WidthAnchor, aspectRatio);
                _heightConstraint.Priority = 1000;
                _heightConstraint.Active = true;
                AddConstraint(_heightConstraint);
            }
        }
    }
}

如果您阅读 UIImageView 上的文档,他们会说:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM