簡體   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