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

问题描述 投票:0回答:2

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

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

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

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

我已经突出显示了蓝色背景来演示这个问题

不良行为:未左对齐,文本之间有多余空间

期望的行为:没有浪费空间,一切都排成一行

如上所示,蓝色图像视图为 20 x 40 点,Content Mode 设置为 Aspect Fit。

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

ios uiimageview
2个回答
1
投票

要获得

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

所以,假设我们有一个图像

100 x 100
...我们说:

  • img.size.width / img.size.height == 1.0
  • widthAnchor
    等于
    20.0 * 1.0
  • 生成的图像视图框架大小为
    20, 20

如果我们的图片尺寸是

250 x 100

  • img.size.width / img.size.height == 2.5
  • widthAnchor
    等于
    20.0 * 2.5
  • 生成的图像视图框架大小为
    50, 20

假设您有一个

UIView
子类,其中约束在初始化期间设置,并且您正在 then 设置图像,您想要使用“可修改”约束作为视图的 var / 属性:

class SomeCustomView: UIView {
    
    // we'll update this constraint when the small image is set
    private var smallImageWidthConstraint: NSLayoutConstraint!

在初始约束设置期间:

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

设置

.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
    

这是一个简单的例子,使用这 3 张图片:

在资产中,我将它们命名为

p272x120
p91x200
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))"
    }
    
}

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

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),
        ])
        
    }
    
}

我们得到这个输出:


0
投票

使用 DonMag 的回答,我想出了自己的解决方案。我没有测试边缘情况或任何东西,但它适合我使用。我在界面生成器中设置了固定高度和内容模式以适应方面

@IBDesignable
@objc class UIVariableWidthImageView : UIImageView {
    
    override var image: UIImage! {
        get {
            return super.image
        }
        set {
            super.image = newValue
            
            widthConstraint?.isActive = false
            if newValue != nil {
                widthConstraint = self.widthAnchor.constraint(equalTo: self.heightAnchor, multiplier: newValue.size.width / newValue.size.height)
                widthConstraint?.isActive = true
            }
        }
    }
        
    private var widthConstraint: NSLayoutConstraint?
}
© www.soinside.com 2019 - 2024. All rights reserved.