具有固定高度的 UIImageView:如何使图像视图宽度增大或缩小以匹配图像的宽高比?

Sta*_*Rat -2 uiimageview ios

我不提供代码,因为这纯粹是一个概念性的“如何实现这个”问题。但我在下面列出了我的尝试和限制。

我尝试了几种不同的策略来实现这一目标。基本上,我有可变宽度的图像,并且希望图像在左侧完美对齐,并与右侧的标签保持 8px 间距。请看下面的图片!

视图本身位于垂直 UIStackView 内。这一行是一个水平的 UIStackView,但我也用一个简单的 UIView 容器尝试过。唯一的约束设置在 UIImageView 上,固定高度为 20px。但我正在寻找任何可行的策略来实现可变宽度、一致的边距行为。

我尝试将 UIImageView 上的 compressionResistence/contentHuggingPriority 设置为低,以及两者之间的每个组合。这是不可能的吗?

我突出显示了蓝色背景来演示该问题

不良行为:未左对齐、文本之间有多余空间 错误的图像:未左对齐,文本之间有多余空间

期望的行为:没有浪费空间,一切都排列整齐 正确的图像:没有浪费空间,一切都排列整齐

如上所示,蓝色图像视图为 20 x 40 点,内容模式设置为宽高比适合。

如何使图像视图宽度增大或缩小以匹配图像的纵横比?

Don*_*Mag 5

要获得UIImageView与图像长宽比匹配的尺寸,您可以将宽度约束设置为高度约束的倍数。

如果我们希望图像视图的高度为 20 点,宽度为“宽高比”:

imageView.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: img.size.width / img.size.height).isActive = true
Run Code Online (Sandbox Code Playgroud)

所以,假设我们有一个图像100 x 100......我们说:

  • img.size.width / img.size.height == 1.0
  • widthAnchor等于20.0 * 1.0
  • 生成的 imageView 帧大小为20, 20

如果我们的图像大小是250 x 100

  • img.size.width / img.size.height == 2.5
  • widthAnchor等于20.0 * 2.5
  • 生成的 imageView 帧大小为50, 20

假设您有一个UIView子类,其中在 init 期间设置了约束,然后设置图像,您希望使用“可修改”约束作为视图的 var / 属性:

class SomeCustomView: UIView {
    
    // we'll update this constraint when the small image is set
    private var smallImageWidthConstraint: NSLayoutConstraint!
Run Code Online (Sandbox Code Playgroud)

在初始约束设置期间:

smallImageView.heightAnchor.constraint(equalToConstant: 20.0).isActive = true

// small image width constraint - will be modified when we set the image
//  small image default is 20x40
smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalToConstant: 40.0)
smallImageWidthConstraint.isActive = true
Run Code Online (Sandbox Code Playgroud)

设置后.image,我们更新该约束:

// de-activate
smallImageWidthConstraint.isActive = false

// set width proportional to height to match image aspect ratio
smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalTo: smallImageView.heightAnchor, multiplier: img.size.width / img.size.height)

// re-activate
smallImageWidthConstraint.isActive = true
    
Run Code Online (Sandbox Code Playgroud)

这是一个使用这 3 个图像的简单示例:

在此输入图像描述

在此输入图像描述

在此输入图像描述

在资产中,我将它们命名为p272x120p91x200因为p232x209这些是实际的图像尺寸。

使用此自定义视图:

class SomeCustomView: UIView {
    
    public var bigImage: UIImage? {
        didSet { bigImageView.image = bigImage }
    }
    public var topString: String = "" {
        didSet { topLabel.text = topString }
    }
    public var sideString: String = "" {
        didSet { sideLabel.text = sideString }
    }
    
    // when we set the small image, we also update the width constraint
    public var smallImage: UIImage? {
        didSet {
            smallImageView.image = smallImage
            // unwrap optional
            if let img = smallImage {
                // de-activate
                smallImageWidthConstraint.isActive = false
                // set width proportional to height to match image aspect ratio
                smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalTo: smallImageView.heightAnchor, multiplier: img.size.width / img.size.height)
                // re-activate
                smallImageWidthConstraint.isActive = true
            }
        }
    }

    private let bigImageView = UIImageView()
    private let smallImageView = UIImageView()
    private let topLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 15.0, weight: .bold)
        v.numberOfLines = 0
        return v
    }()
    private let subLabel: UILabel = {
        let v = UILabel()
        v.font = .italicSystemFont(ofSize: 14.0)
        v.numberOfLines = 0
        return v
    }()
    private let sideLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 12.0, weight: .bold)
        v.numberOfLines = 0
        return v
    }()
    
    // we'll update this constraint when the small image is set
    private var smallImageWidthConstraint: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        // horizontal stack view to hold
        //  small image and side label
        let hStack = UIStackView(arrangedSubviews: [smallImageView, sideLabel])
        hStack.spacing = 8
        hStack.alignment = .top

        // vertical stack view to hold labels and ssmall image
        let vStack = UIStackView(arrangedSubviews: [topLabel, subLabel, hStack])
        vStack.axis = .vertical
        vStack.spacing = 8

        [bigImageView, vStack].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            addSubview(v)
        }

        // small image width constraint - will be modified when we set the image
        //  small image default is 20x40
        smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalToConstant: 40.0)
        
        NSLayoutConstraint.activate([
            
            // big image 80x80
            bigImageView.widthAnchor.constraint(equalToConstant: 80.0),
            bigImageView.heightAnchor.constraint(equalToConstant: 80.0),
            // leading
            bigImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
            // center vertically
            bigImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            // at least 12-points top/bottom
            bigImageView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 12.0),
            bigImageView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -12.0),
            
            // vStack leading 8-points from big image
            vStack.leadingAnchor.constraint(equalTo: bigImageView.trailingAnchor, constant: 8.0),
            // center vStack vertically
            vStack.centerYAnchor.constraint(equalTo: centerYAnchor),
            // at least 12-points top/bottom
            vStack.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 12.0),
            vStack.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -12.0),
            // trailing
            vStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
            
            // small image height 20-points
            smallImageView.heightAnchor.constraint(equalToConstant: 20.0),
            // activate the "changeable" width constraint
            smallImageWidthConstraint,
            
        ])
        
        // let's set some properties
        
        backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        
        // edit this after development...

        // big image will be set by controller
        bigImageView.backgroundColor = .systemRed
        if let img = UIImage(systemName: "swift") {
            bigImageView.image = img
        }

        // these will be .clear
        smallImageView.backgroundColor = .systemBlue
        topLabel.backgroundColor = .yellow
        subLabel.backgroundColor = .cyan
        sideLabel.backgroundColor = .green
        
    }
    
    // this is here during development to show
    //  the resulting small imageView size
    override func layoutSubviews() {
        super.layoutSubviews()

        // let's use Int values, so we don't output .33333333...
        let w = Int(smallImageView.frame.width)
        let h = Int(smallImageView.frame.height)
        subLabel.text = "Actual size (rounded): (w: \(w), h: \(h))"
    }
    
}
Run Code Online (Sandbox Code Playgroud)

和这个示例控制器(将自定义视图放在垂直堆栈视图中):

class AspectImageVC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = 20.0

        let myData: [[String]] = [
            ["p272x120", "Wide Image", "Nintendo Switch"],
            ["p91x200", "Tall, Narrow Image", "Left Controller"],
            ["p232x209", "Square-ish Image", "Nintendo Cube"],
            ["", "No Image", "So we can see default 20x40 small image view size"],
        ]

        myData.forEach { d in

            let imgName = d[0]
            let top = d[1]
            let side = d[2]
            
            let someView = SomeCustomView()
            
            if imgName.isEmpty {
                someView.topString = top
            } else {
                someView.topString = imgName + " " + top
                // safely load the image
                if let img = UIImage(named: imgName) {
                    someView.smallImage = img
                }
            }
            someView.sideString = side

            stackView.addArrangedSubview(someView)

        }

        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
        ])
        
    }
    
}
Run Code Online (Sandbox Code Playgroud)

我们得到这个输出:

在此输入图像描述


编辑-回应评论...

视图可以具有多个“相同”约束。

如果您希望“小图像视图”的最大宽度为 40 点...

添加lessThanOrEqualToConstant宽度约束:

smallImageView.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
smallImageView.widthAnchor.constraint(lessThanOrEqualToConstant: 40.0).isActive = true

// small image width constraint - will be modified when we set the image
//  small image default is 20x40
smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalToConstant: 40.0)
smallImageWidthConstraint.isActive = true
Run Code Online (Sandbox Code Playgroud)

默认情况下,约束有.priority = .required. 所以,当我们设置图像并修改宽度约束时:

// de-activate
smallImageWidthConstraint.isActive = false
// set width proportional to height to match image aspect ratio
smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalTo: smallImageView.heightAnchor, multiplier: img.size.width / img.size.height)
// use less-than-required so we can limit its width
smallImageWidthConstraint.priority = .required - 1
// re-activate
smallImageWidthConstraint.isActive = true
Run Code Online (Sandbox Code Playgroud)

因此,SomeCustomView经过这些修改:

class SomeCustomView: UIView {
    
    public var bigImage: UIImage? {
        didSet { bigImageView.image = bigImage }
    }
    public var topString: String = "" {
        didSet { topLabel.text = topString }
    }
    public var sideString: String = "" {
        didSet { sideLabel.text = sideString }
    }
    
    // when we set the small image, we also update the width constraint
    public var smallImage: UIImage? {
        didSet {
            smallImageView.image = smallImage
            // unwrap optional
            if let img = smallImage {
                // de-activate
                smallImageWidthConstraint.isActive = false
                // set width proportional to height to match image aspect ratio
                smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalTo: smallImageView.heightAnchor, multiplier: img.size.width / img.size.height)
                // use less-than-required so we can limit its width
                smallImageWidthConstraint.priority = .required - 1
                // re-activate
                smallImageWidthConstraint.isActive = true
            }
        }
    }

    private let bigImageView = UIImageView()
    private let smallImageView: UIImageView = {
        let v = UIImageView()
        v.contentMode = .scaleAspectFit
        return v
    }()
    private let topLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 15.0, weight: .bold)
        v.numberOfLines = 0
        return v
    }()
    private let subLabel: UILabel = {
        let v = UILabel()
        v.font = .italicSystemFont(ofSize: 14.0)
        v.numberOfLines = 0
        return v
    }()
    private let sideLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 12.0, weight: .bold)
        v.numberOfLines = 0
        return v
    }()
    
    // we'll update this constraint when the small image is set
    private var smallImageWidthConstraint: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        // horizontal stack view to hold
        //  small image and side label
        let hStack = UIStackView(arrangedSubviews: [smallImageView, sideLabel])
        hStack.spacing = 8
        hStack.alignment = .center

        // vertical stack view to hold labels and ssmall image
        let vStack = UIStackView(arrangedSubviews: [topLabel, subLabel, hStack])
        vStack.axis = .vertical
        vStack.spacing = 8

        [bigImageView, vStack].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            addSubview(v)
        }

        // small image width constraint - will be modified when we set the image
        //  small image default is 20x40
        smallImageWidthConstraint = smallImageView.widthAnchor.constraint(equalToConstant: 40.0)
        
        NSLayoutConstraint.activate([
            
            // big image 80x80
            bigImageView.widthAnchor.constraint(equalToConstant: 80.0),
            bigImageView.heightAnchor.constraint(equalToConstant: 80.0),
            // leading
            bigImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
            // center vertically
            bigImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            // at least 12-points top/bottom
            bigImageView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 12.0),
            bigImageView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -12.0),
            
            // vStack leading 8-points from big image
            vStack.leadingAnchor.constraint(equalTo: bigImageView.trailingAnchor, constant: 8.0),
            // center vStack vertically
            vStack.centerYAnchor.constraint(equalTo: centerYAnchor),
            // at least 12-points top/bottom
            vStack.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 12.0),
            vStack.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -12.0),
            // trailing
            vStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
            
            // small image height 20-points
            smallImageView.heightAnchor.constraint(equalToConstant: 20.0),
            // MAX width of 40-points
            smallImageView.widthAnchor.constraint(lessThanOrEqualToConstant: 40.0),
            // activate the "changeable" width constraint
            smallImageWidthConstraint,
            
        ])
        
        // let's set some properties
        
        backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        
        // edit this after development...

        // big image will be set by controller
        bigImageView.backgroundColor = .systemRed
        bigImageView.tintColor = .systemYellow
        if let img = UIImage(systemName: "swift") {
            bigImageView.image = img
        }

        // these will be .clear
        smallImageView.backgroundColor = .systemBlue
        topLabel.backgroundColor = .yellow
        subLabel.backgroundColor = .cyan
        sideLabel.backgroundColor = .green
        
    }
    
    // this is here during development to show
    //  the resulting small imageView size
    override func layoutSubviews() {
        super.layoutSubviews()

        // let's use Int values, so we don't output .33333333...
        let w = Int(smallImageView.frame.width)
        let h = Int(smallImageView.frame.height)
        subLabel.text = "Actual size (rounded): (w: \(w), h: \(h))"
    }
    
}
Run Code Online (Sandbox Code Playgroud)

现在的输出是:

在此输入图像描述

请注意,顶部图像只有 40 点宽,而不是之前的 45 点宽。