[英]AutoLayout constraints to fit view inside rectangle, preserving a certain aspect ratio (programmatically)
I want to fit an image inside a rectangle that should have a specific aspect ratio. 我想将图像放置在应该具有特定长宽比的矩形内。 No matter what it is, it should find a form to fit inside the rectangle.
不管它是什么,它都应该找到适合矩形内部的形式。 I played around in storyboard and got this:
我在情节提要中玩了一下,得到了:
The ones with the dotted border have low priority (250). 带有虚线边框的优先级较低(250)。 This works in the storyboard.
这在情节提要中起作用。 However, I need to create these constraints programmatically, so I tried it like this (I'm using SnapKit , which simply provides better
AutoLayout
syntax. It should be self-explaining): 但是,我需要以编程方式创建这些约束,所以我这样尝试了(我使用的是SnapKit ,它只是提供了更好的
AutoLayout
语法。这应该是不言自明的):
let topView = UIView()
topView.translatesAutoresizingMaskIntoConstraints = false
topView.backgroundColor = .gray
view.addSubview(topView)
topView.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.left.equalToSuperview()
make.trailing.equalToSuperview()
make.height.equalTo(250)
}
// This view should have a specific aspect ratio and fit inside topView
let holderView = UIView()
holderView.translatesAutoresizingMaskIntoConstraints = false
holderView.backgroundColor = .red
topView.addSubview(holderView)
holderView.snp.makeConstraints { (make) in
make.center.equalToSuperview() // If I remove this one, there's no auto-layout issue, but then it's offset
make.edges.equalToSuperview().priority(250) // sets leading, trailing, top and bottom
make.edges.greaterThanOrEqualToSuperview().priority(1000)
make.width.equalTo(holderView.snp.height).multipliedBy(3/2)
}
If you paste this into an empty ViewController and start it up, you get these issues: 如果将其粘贴到一个空的ViewController中并启动它,则会出现以下问题:
2018-03-16 15:38:50.188867+0100 DemoProject[11298:850932] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
"<SnapKit.LayoutConstraint:0x6000000a7c80@ViewController.swift#24 UIView:0x7fcd82d12440.left == UIView:0x7fcd82d12640.left>",
"<SnapKit.LayoutConstraint:0x6000000a7ce0@ViewController.swift#25 UIView:0x7fcd82d12440.trailing == UIView:0x7fcd82d12640.trailing>",
"<SnapKit.LayoutConstraint:0x6000000a7d40@ViewController.swift#26 UIView:0x7fcd82d12440.height == 250.0>",
"<SnapKit.LayoutConstraint:0x6000000a7da0@ViewController.swift#35 UIView:0x7fcd8580dad0.centerX == UIView:0x7fcd82d12440.centerX>",
"<SnapKit.LayoutConstraint:0x6000000a7e00@ViewController.swift#35 UIView:0x7fcd8580dad0.centerY == UIView:0x7fcd82d12440.centerY>",
"<SnapKit.LayoutConstraint:0x6000000a8580@ViewController.swift#37 UIView:0x7fcd8580dad0.top >= UIView:0x7fcd82d12440.top>",
"<SnapKit.LayoutConstraint:0x6000000a8a60@ViewController.swift#37 UIView:0x7fcd8580dad0.right >= UIView:0x7fcd82d12440.right>",
"<SnapKit.LayoutConstraint:0x6000000a9360@ViewController.swift#38 UIView:0x7fcd8580dad0.width == UIView:0x7fcd8580dad0.height>",
"<NSLayoutConstraint:0x600000092cf0 'UIView-Encapsulated-Layout-Width' UIView:0x7fcd82d12640.width == 414 (active)>"
Will attempt to recover by breaking constraint <SnapKit.LayoutConstraint:0x6000000a9360@ViewController.swift#38 UIView:0x7fcd8580dad0.width == UIView:0x7fcd8580dad0.height>
This doesn't show up, when I remove the centering constraint make.center.equalToSuperview()
. 当我删除居中约束
make.center.equalToSuperview()
时,这不会显示。 But then, it's misplaced. 但随后,它放错了位置。
What is different between the storyboard and my code? 情节提要和我的代码之间有什么区别? I don't really understand this.
我不太明白。 I also tried this using the default swift syntax, the result was exactly the same.
我还使用默认的swift语法尝试了此操作,结果完全相同。 So I don't think it's a problem with
SnapKit
所以我认为
SnapKit
问题
Any ideas? 有任何想法吗? Thank you guys for any help.
谢谢大家的帮助。 Let me know if you need any more infos.
让我知道您是否需要更多信息。
EDIT: I mixed something up. 编辑:我混合了一些东西。 It's not about the image and its aspect ratio.
这与图像及其宽高比无关。 It's just about a
UIView
that should maintain a specific aspect ratio while fitting inside a rectangle. 它只是关于一个
UIView
,它应该在安装在矩形内时保持特定的宽高比。 The actual image will just be put into that holderView
. 实际图像将被放入该
holderView
。 Sorry 抱歉
OK - here is one way to do it. 好的-这是一种方法。
Take the "native" size of your subview, calculate the "aspect fit" ratio - that is, the ratio that will fit the width or height to the superview, and scale the other dimension appropriately. 取子视图的“本机”大小,计算“长宽比”比例-即适合宽度或高度到父视图的比例,并适当缩放其他尺寸。
Then, use centerXAnchor
and centerYAnchor
to position the subview, and widthAnchor
and heightAnchor
to size it. 然后,使用
centerXAnchor
和centerYAnchor
定位子视图,并使用widthAnchor
和heightAnchor
其大小。
Note: if you're trying to place an image , calculate the aspect fit size from the image size, put the image in an image view, set the image view scale mode to fill
, and finally apply the constraints to the image view. 注意:如果要放置图像 ,请根据图像大小计算宽高比合适的大小,将图像放入图像视图,将图像视图缩放模式设置为
fill
,最后将约束应用于图像视图。
You should be able to run this example as-is. 您应该能够按原样运行此示例。 Just play around with the "native" size values at the top to see how it fits the subview into the superview.
只需在顶部使用“本机”大小值即可查看它如何将子视图适合到超级视图中。
public class AspectFitViewController : UIViewController {
// "native" size for the holderView
let hViewWidth: CGFloat = 700.0
let hViewHeight: CGFloat = 200.0
let topView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.blue
return v
}()
let holderView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.cyan
return v
}()
public override func viewDidLoad() {
super.viewDidLoad()
view.bounds = CGRect(x: 0, y: 0, width: 400, height: 600)
view.backgroundColor = .yellow
// add topView
view.addSubview(topView)
// pin topView to leading / top / trailing
topView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
topView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
topView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true
// explicit height for topView
topView.heightAnchor.constraint(equalToConstant: 250.0).isActive = true
// add holderView to topView
topView.addSubview(holderView)
// center X and Y
holderView.centerXAnchor.constraint(equalTo: topView.centerXAnchor, constant: 0.0).isActive = true
holderView.centerYAnchor.constraint(equalTo: topView.centerYAnchor, constant: 0.0).isActive = true
// holderView's width and height will be calculated in viewDidAppear
// after topView has been laid-out by the auto-layout engine
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let aspectWidth = topView.bounds.size.width / hViewWidth
let aspectHeight = topView.bounds.size.height / hViewHeight
let aspectFit = min(aspectWidth, aspectHeight)
let newWidth = hViewWidth * aspectFit
let newHeight = hViewHeight * aspectFit
holderView.widthAnchor.constraint(equalToConstant: newWidth).isActive = true
holderView.heightAnchor.constraint(equalToConstant: newHeight).isActive = true
}
}
Edit: 编辑:
After clarification... this can be accomplished by constraints only. 在澄清之后……这只能通过约束来实现。 The key is that "Priority 1000"
top
and leading
constraints must be .greaterThanOrEqual
to zero, and the bottom
and trailing
constraints must be .lessThanOrEqual
to zero. 关键是“ Priority 1000”
top
和leading
约束必须为.greaterThanOrEqual
等于零,而bottom
和trailing
约束则必须为.lessThanOrEqual
等于零。
public class AspectFitViewController : UIViewController {
// "native" size for the holderView
let hViewWidth: CGFloat = 700.0
let hViewHeight: CGFloat = 200.0
let topView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.blue
return v
}()
let holderView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.cyan
return v
}()
public override func viewDidLoad() {
super.viewDidLoad()
view.bounds = CGRect(x: 0, y: 0, width: 400, height: 600)
view.backgroundColor = .yellow
// add topView
view.addSubview(topView)
// pin topView to leading / top / trailing
topView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
topView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
topView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true
// explicit height for topView
topView.heightAnchor.constraint(equalToConstant: 250.0).isActive = true
// add holderView to topView
topView.addSubview(holderView)
// center X and Y
holderView.centerXAnchor.constraint(equalTo: topView.centerXAnchor, constant: 0.0).isActive = true
holderView.centerYAnchor.constraint(equalTo: topView.centerYAnchor, constant: 0.0).isActive = true
// aspect ratio size
holderView.widthAnchor.constraint(equalTo: holderView.heightAnchor, multiplier: hViewWidth / hViewHeight).isActive = true
// two constraints for each side...
// the .equal constraints need .defaultLow priority
// top and leading constraints must be .greaterThanOrEqual to 0
// bottom and trailing constraints must be .lessThanOrEqual to 0
let topA = NSLayoutConstraint(item: holderView, attribute: .top, relatedBy: .greaterThanOrEqual, toItem: topView, attribute: .top, multiplier: 1.0, constant: 0.0)
let topB = NSLayoutConstraint(item: holderView, attribute: .top, relatedBy: .equal, toItem: topView, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomA = NSLayoutConstraint(item: holderView, attribute: .bottom, relatedBy: .lessThanOrEqual, toItem: topView, attribute: .bottom, multiplier: 1.0, constant: 0.0)
let bottomB = NSLayoutConstraint(item: holderView, attribute: .bottom, relatedBy: .equal, toItem: topView, attribute: .bottom, multiplier: 1.0, constant: 0.0)
let leadingA = NSLayoutConstraint(item: holderView, attribute: .leading, relatedBy: .greaterThanOrEqual, toItem: topView, attribute: .leading, multiplier: 1.0, constant: 0.0)
let leadingB = NSLayoutConstraint(item: holderView, attribute: .leading, relatedBy: .equal, toItem: topView, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingA = NSLayoutConstraint(item: holderView, attribute: .trailing, relatedBy: .lessThanOrEqual, toItem: topView, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let trailingB = NSLayoutConstraint(item: holderView, attribute: .trailing, relatedBy: .equal, toItem: topView, attribute: .trailing, multiplier: 1.0, constant: 0.0)
topB.priority = .defaultLow
bottomB.priority = .defaultLow
leadingB.priority = .defaultLow
trailingB.priority = .defaultLow
NSLayoutConstraint.activate([
topA, topB,
bottomA, bottomB,
leadingA, leadingB,
trailingA, trailingB
])
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.