我不提供代码,因为这纯粹是一个概念性的“如何实现这个”问题。但我在下面列出了我的尝试和限制。
我尝试了几种不同的策略来实现这一目标。基本上,我有可变宽度的图像,并且希望图像在左侧完美对齐,并与右侧的标签保持 8px 间距。请看下面的图片!
视图本身位于垂直 UIStackView 内。这一行是一个水平的 UIStackView,但我也用一个简单的 UIView 容器尝试过。唯一的约束设置在 UIImageView 上,固定高度为 20px。但我正在寻找任何可行的策略来实现可变宽度、一致的边距行为。
我尝试将 UIImageView 上的 compressionResistence/contentHuggingPriority 设置为低,以及两者之间的每个组合。这是不可能的吗?
我突出显示了蓝色背景来演示该问题
如上所示,蓝色图像视图为 20 x 40 点,内容模式设置为宽高比适合。
如何使图像视图宽度增大或缩小以匹配图像的纵横比?
要获得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.0widthAnchor等于20.0 * 1.020, 20如果我们的图像大小是250 x 100
img.size.width / img.size.height == 2.5widthAnchor等于20.0 * 2.550, 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 个图像的简单示例:
在资产中,我将它们命名为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))"
}
}
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 点宽。
| 归档时间: |
|
| 查看次数: |
1153 次 |
| 最近记录: |