简体   繁体   English

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

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

I want my UIImageView to grow or shrink depending on the size of what the actual image it's displaying is.我希望我的UIImageView根据它显示的实际图像的大小来增长或缩小。 But I want it to stay vertically centered and 10pts from the leading edge of the superview.但我希望它保持垂直居中,距离超级视图的前缘 10pts。

However, if I set these two constraints it complains I haven't set up enough constraints to satisfy Auto Layout.但是,如果我设置这两个约束,它会抱怨我没有设置足够的约束来满足自动布局。 To me it seems to perfectly describe what I want.对我来说,它似乎完美地描述了我想要的东西。 What am I missing?我错过了什么?

The image view's intrinsic size is already dependent on the size of the image.图像视图的固有大小已经取决于图像的大小。 Your assumptions (and constraints are correct).您的假设(和约束是正确的)。

However, if you've set up your image view in interface builder and have not provided it with an image, then the layout system (interface builder) won't know how big your image view is supposed to be at compile time.但是,如果您已经在界面构建器中设置了图像视图并且没有为其提供图像,那么布局系统(界面构建器)将不知道您的图像视图在编译时应该有多大。 Your layout is ambiguous because your image view could be many sizes.您的布局不明确,因为您的图像视图可能有多种尺寸。 This is what throws the errors.这就是引发错误的原因。

Once you set your image view's image property, then the image view's intrinsic size is defined by the size of the image.一旦你设置了图像视图的图像属性,图像视图的内在尺寸就由图像的尺寸定义。 If you're setting the view at runtime, then you can do exactly what Anna mentioned and provide interface builder with a "placeholder" intrinsic size in the property inspector of the image view.如果您在运行时设置视图,那么您可以完全按照 Anna 提到的方式执行操作,并在图像视图的属性检查器中为界面构建器提供“占位符”固有大小。 This tells interface builder, "use this size for now, I'll give you a real size later".这告诉界面构建器,“现在使用这个尺寸,稍后我会给你一个真实的尺寸”。 The placeholder constraints are ignored at runtime.占位符约束在运行时被忽略。

Your other option is to assign the image to the image view in interface builder directly (but I assume your images are dynamic, so this won't work for you).您的另一个选择是直接将图像分配给界面构建器中的图像视图(但我假设您的图像是动态的,因此这对您不起作用)。

Here is the essential problem: the only way in which UIImageView interacts with Auto Layout is via its intrinsicContentSize property.这是基本问题: UIImageView与自动布局交互的唯一方式是通过其intrinsicContentSize属性。 That property provides the intrinsic size of the image itself, and nothing more.该属性提供图像本身的内在大小,仅此而已。 This creates three situations, with different solutions.这会产生三种情况,具有不同的解决方案。

case 1: view with image's size案例1:查看图像的大小

In the first case, if you place an UIImageView in a context where external Auto Layout constraints affect only its position, then the view's intrinsicContentSize will declare it wants to be the size of its image, and Auto Layout will automatically resize the view to equal the size of its image.在第一种情况下,如果您将UIImageView放置在外部自动布局约束仅影响其位置的上下文中,则视图的intrinsicContentSize内容大小将声明它希望是其图像的大小,并且自动布局将自动调整视图的大小以使其等于其图像的大小。 This is sometimes exactly what you want, and then life is good!这有时正是你想要的,然后生活就是美好的!

case 2: fully constrained view size, preserving image's aspect ratio情况 2:完全约束视图大小,保留图像的纵横比

At other times, you're in a different case.在其他时候,你处于不同的情况。 You know exactly the size you want the image view to take, but you also want it to preserve the aspect ratio of the displayed image.您确切地知道您希望图像视图采用的尺寸,但您还希望它保留所显示图像的纵横比。 In this case, you can use Auto Layout to constrain the size of the image, while setting the old contentMode property to .scaleAspectFit on the image view.在这种情况下,您可以使用自动布局来限制图像的大小,同时在图像视图.scaleAspectFit旧的contentMode属性设置为.scaleAspectFit That property will cause the image view to rescale the displayed image, adding padding as needed.该属性将导致图像视图重新缩放显示的图像,根据需要添加填充。 And if this is what you want, life is good!如果这就是你想要的,生活就是美好的!

case 3: partially constrained view size, adapting to image aspect ratio case 3:部分约束视图大小,适应图像纵横比

But often times, you're in the tricky third case.但很多时候,你处于棘手的第三种情况。 You want to use Auto Layout to partially determine the size of the view, while allowing the image's aspect ratio to determine the other part.您希望使用自动布局来部分确定视图的大小,同时允许图像的纵横比来确定其他部分。 For instance, you might want to use Auto Layout constraints to determine one dimension of the size (like the width, in a vertical timeline scroll), while relying on the view to tell Auto Layout that it wants a height matching the image's aspect ratio (so the scrollable items are not too short or too tall).例如,您可能希望使用自动布局约束来确定尺寸的一个维度(如宽度,在垂直时间轴滚动中),同时依靠视图告诉自动布局它需要与图像纵横比匹配的高度(所以可滚动的项目不会太短或太高)。

You cannot configure an ordinary image view to do this because because an image view only communicates its intrinsicContentSize .你不能配置一个普通的图像视图来做到这一点,因为图像视图只传达它的intrinsicContentSize So if you put an image view in a context where Auto Layout constrains one dimension (for instance, constraining it to a short width), then the image view does not tell Auto Layout it wants its height rescaled in tandem.因此,如果您将图像视图放在自动布局约束一维的上下文中(例如,将其约束为较短的宽度),那么图像视图不会告诉自动布局它希望其高度同步重新缩放。 It continues to report its intrinsic content size, with the unmodified height of the image itself.它继续报告其内在内容大小,图像本身的高度未修改。

In order to configure the image view to tell Auto Layout that it wants to take on the aspect ratio of the image it contains, you need to add a new constraint to that effect.为了配置图像视图以告诉自动布局它想要采用它包含的图像的纵横比,您需要为该效果添加一个新约束。 Furthermore, you will need to update that constraint whenever the image itself is updated.此外,每当更新图像本身时,您都需要更新该约束。 You can build a UIImageView subclass that does this, so the behavior is automatic.您可以构建一个执行此操作的UIImageView子类,因此该行为是自动的。 Here's an example:下面是一个例子:

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

More comment is available at this gist更多评论可在 此要点中找到

For @algal case 3, all I had to do was对于@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)
    }
}

This scales the height component of the intrinsicContentSize.这将缩放内在内容大小的高度组件。 (-1.0, -1.0) is returned when there is no image because this is the default behavior in the superclass. (-1.0, -1.0) 在没有图像时返回,因为这是超类中的默认行为。

Also, I did not need to set UITableViewAutomaticDimension for the table with the cell containing the image view, and the cell still sizes automatically.此外,我不需要为包含图像视图的单元格的表格设置UITableViewAutomaticDimension ,并且单元格仍然会自动调整大小。 The image view's content mode is Aspect Fit .图像视图的内容模式是Aspect Fit

Here is an AspectFit UIImageView -derived implementation that works with Auto Layout when only one dimension is constrained.这是一个AspectFit UIImageView派生的实现,当只有一个维度受到约束时,它可以与自动布局一起使用。 The other one will be set automatically.另一个将自动设置。 It will keep image aspect ratio and doesn't add any margins around the image.它将保持图像纵横比,并且不会在图像周围添加任何边距。

It's tweaked Objective-C implementation of @algal idea with the following differences:它调整了@algal 想法的Objective-C 实现,具有以下差异:

  • Expression priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0 which evaluates to 150 isn't enough to beat the priority of 1000 of the default image content size constraint.表达式priority = (UILayoutPriorityDefaultLow + UILayoutPriorityFittingSizeLevel) / 2.0评估为150不足以击败默认图像内容大小约束的优先级1000 So aspect constraint priority was increased to 1000 as well.因此方面约束优先级也增加到1000
  • it removes\\re-adds the aspect ratio constraint only if necessary.它仅在必要时删除\\重新添加纵横比约束。 Keep in mind that this is heavy operation so there is no need to do so if aspect ratio is the same.请记住,这是一项繁重的操作,因此如果纵横比相同,则无需这样做。 Moreover, image setter could be called multiple times, so it's a good idea to not cause extra layout calculations here.此外, image设置器可能会被多次调用,因此最好不要在此处进行额外的布局计算。
  • not sure why we need to override required public init?(coder aDecoder: NSCoder) and public override init(frame:CGRect) , so these two are taken out.不知道为什么我们需要覆盖required public init?(coder aDecoder: NSCoder)public override init(frame:CGRect) ,所以把这两个public override init(frame:CGRect) They aren't overridden by UIImageView , that's why there is no point of adding the aspect constraint until an image is set.它们不会被UIImageView覆盖,这就是为什么在设置图像之前没有添加方面约束的原因。

AspectKeepUIImageView.h : 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 : 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

I used @algal's excellent answer (case #3) and improved it for my use case.我使用了@algal 的优秀答案(案例#3)并针对我的用例进行了改进。

I wanted to be able to set min and max ratio constraints .我希望能够设置最小和最大比率约束 So if I have a minimum ratio constraint of 1.0 and set a picture that is higher than wide, the size should be square.因此,如果我将最小比率约束设置为 1.0 并设置高于宽度的图片,则大小应为正方形。 Also it should fill the UIImageView in those cases.在这些情况下,它也应该填充UIImageView

This works even in Interface Builder.这甚至在 Interface Builder 中也有效。 Just add a UIImageView , set RatioBasedImageView as custom class in the identity inspector and set the max and min aspect ratios in the attributes inspector.只需添加一个UIImageView ,在身份检查器中将RatioBasedImageView设置为自定义类,并在属性检查器中设置最大和最小纵横比。

Here's my code.这是我的代码。

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

I usually add no image height and width constraints with value 0 and priority less than UIImageView 's contentCompressionResistancePriority , which by default is 750 .我通常不添加值为0priority小于UIImageViewcontentCompressionResistancePriority图像高度和宽度约束,默认情况下为750 These two constraints will activate only when there is no image, and will take care of ambiguous constraints warnings both at run time and compile time.这两个约束只有在没有图像时才会激活,并且会在运行时和编译时处理不明确的约束警告。

Pretty straight forward to add these in IB.在 IB 中添加这些非常简单。 Programatically, it should look something like below.以编程方式,它应该如下所示。

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

If anyone comes here wanting a simple copy+paste for Xamarin heres @algal's code (with some of @alexander's changes) converted to .Net如果有人来这里想要 Xamarin 的简单复制+粘贴,@algal 的代码(带有一些 @alexander 的更改)转换为 .Net

Also I've changed it to constrain the Height based on the Width + Aspect Ratio as that fit all of my use cases.此外,我已将其更改为基于宽度 + 纵横比限制高度,因为这适合我的所有用例。

/// <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);
            }
        }
    }
}

If you read the docs on UIImageView they say:如果您阅读 UIImageView 上的文档,他们会说:

Setting the image property does not change the size of a UIImageView.设置图像属性不会改变 UIImageView 的大小。 Call sizeToFit to adjust the size of the view to match the image.调用sizeToFit来调整视图的大小以匹配图像。

暂无
暂无

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

相关问题 如何使用自动布局更改按钮的大小? - How do I change button's size with Auto-layout? 如何同时为UIImageView的图像和UIImageView本身设置动画? - How do I simultaneously animate a UIImageView's image and the UIImageView itself? 如何根据动态图像URL的高度来调整包含在UITableview的自定义单元格中的UIImageview的大小? - How to resize an UIImageview, enclosed within a UITableview's custom cell, depending upon dynamic image URL's height? 如何以编程方式进行此自动布局? - How do I programmatically make this auto layout? 如何在iOS8上使UICollectionViewCell高度动态(带有自动布局) - How do I make the UICollectionViewCell height dynamic (w/Auto Layout) on iOS8 在 XCode 中使用 Auto Layout 根据大小使整个视图缩放 - Using Auto Layout in XCode to make the entire view scale depending on the size 如何使UIImageView的宽度不比其包含的视图宽? - How do I make a UIImageView not be wider than the view it's contained in? 如何使UIView扩展为包含具有自动布局和IB的UIImageView或UITextView? - How to make a UIView expand to contain a UIImageView or a UITextView with auto layout and IB? 使用自动布局自定义每个设备的UIImageView大小 - Customize UIImageView size for each device with Auto Layout 如何告知所有自动布局约束自行更新? (例如,当用户更新其系统大小/动态类型时)? - How do I tell all my Auto Layout constraints to update themselves? (For example when user updates their system size/Dynamic Type)?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM