AutoLayout约束以适应矩形内的视图,保持一定的纵横比(以编程方式)
Posted
技术标签:
【中文标题】AutoLayout约束以适应矩形内的视图,保持一定的纵横比(以编程方式)【英文标题】:AutoLayout constraints to fit view inside rectangle, preserving a certain aspect ratio (programmatically) 【发布时间】:2018-03-16 15:12:25 【问题描述】:我想将图像放入一个应该具有特定纵横比的矩形内。不管它是什么,它都应该找到一个适合矩形的形式。我在故事板中玩耍并得到了这个:
虚线边框的优先级较低(250)。
这在故事板中有效。但是,我需要以编程方式创建这些约束,所以我这样尝试(我使用的是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)
如果您将其粘贴到一个空的 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>
当我删除居中约束make.center.equalToSuperview()
时,这不会出现。但是,它放错了位置。
故事板和我的代码有什么不同?我真的不明白这一点。我也尝试使用默认的 swift 语法,结果完全一样。所以我认为SnapKit
没有问题
有什么想法吗?谢谢你们的帮助。如果您需要更多信息,请告诉我。
编辑:我混淆了一些东西。这与图像及其纵横比无关。这只是一个UIView
,它应该在适合矩形的同时保持特定的纵横比。实际图像将被放入holderView
。对不起
【问题讨论】:
为什么 imageView 不简单地链接到支架边缘并使用.scaleAspectFit
的任何原因?
很难解释,但我会尝试:我有一个collection view
,里面充满了不同大小的单元格。每个单元格都被设置为.scaleAspectFill
的UIImageView
(链接到单元格的边缘)覆盖。现在我需要在另一个view controller
中以较小的形式表示带有图像的单元格,您可以在其中移动它以放置/旋转/缩放图像(aspectFill '缩放'图像以适应,你应该能够有点说它是如何放置在单元格中的)
我意识到这与ImageView
无关,真的。我要编辑标题。该图像确实只是链接到holderView
s 边缘。它的持有者视图必须具有单元格的纵横比。抱歉,我弄混了
你有太多的限制。您可以约束两条边和宽度/高度或中心和宽度/高度或四个边(如果您使用纵横比约束,则仅设置宽度或高度)
看起来您正在尝试将 imageView 的 width
设置为等于 imageView * 3/2 的 height
... 但您也是 将 imageView 的边缘设置为其父视图。您可能想要设置一个或另一个,而不是两者。
【参考方案1】:
好的 - 这是一种方法。
获取子视图的“原生”大小,计算“纵横比”比率 - 即适合父视图的宽度或高度的比率,并适当缩放其他维度。
然后,使用centerXAnchor
和centerYAnchor
定位子视图,并使用widthAnchor
和heightAnchor
调整大小。
注意:如果您要放置图像,请根据图像大小计算宽高比大小,将图像放入图像视图中,将图像视图缩放模式设置为fill
,最后将约束应用于图像视图。
您应该能够按原样运行此示例。只需使用顶部的“本机”大小值,看看它如何将子视图融入到父视图中。
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
编辑:
澄清后...这可以仅通过约束来完成。关键是“优先级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
])
【讨论】:
感谢您的回答。我以前有一个类似的版本(虽然它不太好用。你的版本运行良好),但是当它应该在不手动计算任何东西的情况下工作时,我发现这有点不方便。 (它使用故事板工作,所以它必须工作也只能使用代码。这就是我不明白的)。但是我想如果没有人知道代码为什么不起作用,我将不得不手动坚持计算宽度/高度。无论如何,谢谢。 @Quantm - 啊 --- 现在我明白了。请参阅我编辑的答案,特别是 cmets。 哇,这确实有效(虽然我认为应该是hViewHeight / hViewWidth
,而不是hViewWidth / hViewHeight
)。但为什么它应该是 lessThanOrEqualTo 呢?我不太明白。这不应该意味着零件可以在屏幕外吗?但非常感谢您的努力。我真的很感激!
哎呀 - 我调换了宽高比...现在已修复。当您在故事板中设置尾随和底部约束时,IB 会自动设置 First Item: Superview, Second Item: Subview... 所以它是>= 0
。从代码中设置它通常更容易根据“子视图到超级视图”来思考,因此您需要将其设为<= 0
。编码为“我希望它距底部 20 分”并且在将其设置为 +20
而不是 -20
时出错非常常见。
ooooohhhh,该死的。对,我从来没想过。说得通。谢谢! :)以上是关于AutoLayout约束以适应矩形内的视图,保持一定的纵横比(以编程方式)的主要内容,如果未能解决你的问题,请参考以下文章
实现一个在autolayout下有宽度约束后,自动确定高度的view
使用 AutoLayout 缩放 UIImageView 以适应宽度
使用Xcode 6中的AutoLayout约束模拟方面适合行为