我不提供代码,因为这纯粹是一个概念性的“如何实现这个”问题。但我在下面列出了我的尝试和限制条件。
我尝试了几种不同的策略来实现这一目标。基本上,我有可变宽度的图像,并希望图像在左侧完美对齐,并与右侧的标签保持 8px 的间距。请看下面的图片!
视图本身位于垂直的 UIStackView 中。这一行是一个水平的 UIStackView,但我也尝试过使用一个简单的 UIView 容器。唯一的约束集是在 UIImageView 上,固定高度为 20px。但我正在寻找任何可行的策略来实现可变宽度、一致的边距行为。
我已经尝试将 UIImageView 的 compressionResistence/contentHuggingPriority 设置得较低,以及两者之间的每个组合。这是不可能的吗?
我已经突出显示了蓝色背景来演示这个问题
如上所示,蓝色图像视图为 20 x 40 点,Content Mode 设置为 Aspect Fit。
如何让图像视图宽度增大或缩小以匹配图像的纵横比?
要获得
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),
])
}
}
我们得到这个输出:
使用 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?
}