在 swift ios 编程约束下,如何指定默认为 50% 高度但可以根据需要缩小的视图?
With swift ios programming constraints, how to specify a view that defaults to 50% of the height but can shrink if needed?
我正在使用 NSLayoutConstraint 来约束视图。我希望它的高度默认占屏幕的 50%,但如果没有足够的空间容纳其他组件(例如横向 iphone),视图可以缩小到高度的 10% .
我正在尝试:
let y1 = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
let y2 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .lessThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
let y3 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)
不幸的是,这只呈现为屏幕高度的 10%。
我对两件事感到困惑:
当我设置这样的模糊约束时,基本上说“在 10% 到 50% 之间”,它如何决定给它多少高度?是否默认为最小值space?
我认为约束必须只有一个解决方案。为什么我没有收到歧义错误,因为从 10% 到 50% 的任何高度在这里都是有效的解决方案?
最后,我如何获得我想要的 50% 的视图,如果需要可以缩小?
非常感谢!
您可以通过更改 50% 高度限制的 Priority
来做到这一点。
我们会告诉 auto-layout 按钮必须 至少 视图高度的 10%。
而且,我们会告诉 auto-layout 我们 想要 按钮占视图高度的 50%,但是:
.priority = .defaultHigh
表示“如果需要,您可以打破此限制。”
所以...
// constrain button Top to view Top
let btnTop = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
// button Height Greater Than Or Equal To 10%
let percent10 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)
// button Height Equal To 50%
let percent50 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal,
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
// let auto-layout break the 50% height constraint if necessary
percent50.priority = .defaultHigh
[btnTop, percent10, percent50].forEach {
[=11=].isActive = true
}
或者,使用更现代的语法...
let btnTop = button.topAnchor.constraint(equalTo: view.topAnchor)
let percent10 = button.heightAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor, multiplier: 0.10)
let percent50 = button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.50)
percent50.priority = .defaultHigh
NSLayoutConstraint.activate([btnTop, percent10, percent50])
现在,无论您有什么其他 UI 元素会减少可用 space,auto-layout 都会将按钮的高度设置为“尽可能接近 50%,但始终至少 10%
这里有一个完整的例子来演示。我使用了两个标签(顶部的蓝色作为“按钮”,底部的红色)。点击会增加红色标签的高度,直到它开始“推高底部”或“压缩”蓝色标签:
class ExampleViewController: UIViewController {
let blueLabel = UILabel()
let redLabel = UILabel()
var viewSafeAreaHeight: CGFloat = 0
var adjustableLabelHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
[blueLabel, redLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
v.textAlignment = .center
v.textColor = .white
v.numberOfLines = 0
}
blueLabel.backgroundColor = .blue
redLabel.backgroundColor = .red
view.addSubview(blueLabel)
view.addSubview(redLabel)
// blueLabel should be 50% of the height if possible
// otherwise, let it shrink to minimum of 10%
// so, we'll constrain redLabel to the bottom of the view
// and give it a Height constraint that we can change
// so it can "compress" blueLabel
// we'll constrain the bottom of blueLabel to stay above the top of redLabel
// let's respect the safe-area
let safeArea = view.safeAreaLayoutGuide
// start by horizontally centering both elements,
// and 75% of the width of the view
blueLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
redLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
blueLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
redLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
// now, let's constrain redLabel to the bottom
redLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
// tell the Bottom of blueLabel to stay Above the top of redLabel
blueLabel.bottomAnchor.constraint(lessThanOrEqualTo: redLabel.topAnchor, constant: 0.0).isActive = true
// next, constrain the top of blueLabel to the top
let blueLabelTop = blueLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 0.0)
// blueLabel height must be At Least 10% of the view
let blue10 = blueLabel.heightAnchor.constraint(greaterThanOrEqualTo: safeArea.heightAnchor, multiplier: 0.10)
// blueLabel should be 50% if possible -- so we'll set the
// Priority on that constraint to less than Required
let blue50 = blueLabel.heightAnchor.constraint(equalTo: safeArea.heightAnchor, multiplier: 0.50)
blue50.priority = .defaultHigh
// start redLabel Height at 100-pts
adjustableLabelHeightConstraint = redLabel.heightAnchor.constraint(equalToConstant: 100.0)
// we'll be increasing the Height constant past the available area,
// so we also need to change its Priority so we don't get
// auto-layout conflict errors
// and, we need to set it GREATER THAN blueLabel's height priority
adjustableLabelHeightConstraint.priority = UILayoutPriority(rawValue: blue50.priority.rawValue + 1)
// activate those constraints
NSLayoutConstraint.activate([blueLabelTop, blue10, blue50, adjustableLabelHeightConstraint])
// add a tap gesture recognizer so we can increas the height of the label
let t = UITapGestureRecognizer(target: self, action: #selector(self.gotTap(_:)))
view.addGestureRecognizer(t)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
viewSafeAreaHeight = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom)
updateLabelText()
}
@objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
adjustableLabelHeightConstraint.constant += 50
updateLabelText()
}
func updateLabelText() -> Void {
let blueHeight = blueLabel.frame.height
let redHeight = redLabel.frame.height
let redConstant = adjustableLabelHeightConstraint.constant
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
percentFormatter.minimumFractionDigits = 2
percentFormatter.maximumFractionDigits = 2
guard let bluePct = percentFormatter.string(for: blueHeight / viewSafeAreaHeight) else { return }
var s = "SafeArea Height: \(viewSafeAreaHeight)"
s += "\n"
s += "Blue Height: \(blueHeight)"
s += "\n"
s += "\(blueHeight) / \(viewSafeAreaHeight) = \(bluePct)"
blueLabel.text = s
s = "Tap to increase..."
s += "\n"
s += "Red Height Constant: \(redConstant)"
s += "\n"
s += "Red Actual Height: \(redHeight)"
redLabel.text = s
}
}
我正在使用 NSLayoutConstraint 来约束视图。我希望它的高度默认占屏幕的 50%,但如果没有足够的空间容纳其他组件(例如横向 iphone),视图可以缩小到高度的 10% .
我正在尝试:
let y1 = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
let y2 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .lessThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
let y3 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)
不幸的是,这只呈现为屏幕高度的 10%。
我对两件事感到困惑:
当我设置这样的模糊约束时,基本上说“在 10% 到 50% 之间”,它如何决定给它多少高度?是否默认为最小值space?
我认为约束必须只有一个解决方案。为什么我没有收到歧义错误,因为从 10% 到 50% 的任何高度在这里都是有效的解决方案?
最后,我如何获得我想要的 50% 的视图,如果需要可以缩小?
非常感谢!
您可以通过更改 50% 高度限制的 Priority
来做到这一点。
我们会告诉 auto-layout 按钮必须 至少 视图高度的 10%。
而且,我们会告诉 auto-layout 我们 想要 按钮占视图高度的 50%,但是:
.priority = .defaultHigh
表示“如果需要,您可以打破此限制。”
所以...
// constrain button Top to view Top
let btnTop = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
// button Height Greater Than Or Equal To 10%
let percent10 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)
// button Height Equal To 50%
let percent50 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal,
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
// let auto-layout break the 50% height constraint if necessary
percent50.priority = .defaultHigh
[btnTop, percent10, percent50].forEach {
[=11=].isActive = true
}
或者,使用更现代的语法...
let btnTop = button.topAnchor.constraint(equalTo: view.topAnchor)
let percent10 = button.heightAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor, multiplier: 0.10)
let percent50 = button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.50)
percent50.priority = .defaultHigh
NSLayoutConstraint.activate([btnTop, percent10, percent50])
现在,无论您有什么其他 UI 元素会减少可用 space,auto-layout 都会将按钮的高度设置为“尽可能接近 50%,但始终至少 10%
这里有一个完整的例子来演示。我使用了两个标签(顶部的蓝色作为“按钮”,底部的红色)。点击会增加红色标签的高度,直到它开始“推高底部”或“压缩”蓝色标签:
class ExampleViewController: UIViewController {
let blueLabel = UILabel()
let redLabel = UILabel()
var viewSafeAreaHeight: CGFloat = 0
var adjustableLabelHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
[blueLabel, redLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
v.textAlignment = .center
v.textColor = .white
v.numberOfLines = 0
}
blueLabel.backgroundColor = .blue
redLabel.backgroundColor = .red
view.addSubview(blueLabel)
view.addSubview(redLabel)
// blueLabel should be 50% of the height if possible
// otherwise, let it shrink to minimum of 10%
// so, we'll constrain redLabel to the bottom of the view
// and give it a Height constraint that we can change
// so it can "compress" blueLabel
// we'll constrain the bottom of blueLabel to stay above the top of redLabel
// let's respect the safe-area
let safeArea = view.safeAreaLayoutGuide
// start by horizontally centering both elements,
// and 75% of the width of the view
blueLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
redLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
blueLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
redLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
// now, let's constrain redLabel to the bottom
redLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
// tell the Bottom of blueLabel to stay Above the top of redLabel
blueLabel.bottomAnchor.constraint(lessThanOrEqualTo: redLabel.topAnchor, constant: 0.0).isActive = true
// next, constrain the top of blueLabel to the top
let blueLabelTop = blueLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 0.0)
// blueLabel height must be At Least 10% of the view
let blue10 = blueLabel.heightAnchor.constraint(greaterThanOrEqualTo: safeArea.heightAnchor, multiplier: 0.10)
// blueLabel should be 50% if possible -- so we'll set the
// Priority on that constraint to less than Required
let blue50 = blueLabel.heightAnchor.constraint(equalTo: safeArea.heightAnchor, multiplier: 0.50)
blue50.priority = .defaultHigh
// start redLabel Height at 100-pts
adjustableLabelHeightConstraint = redLabel.heightAnchor.constraint(equalToConstant: 100.0)
// we'll be increasing the Height constant past the available area,
// so we also need to change its Priority so we don't get
// auto-layout conflict errors
// and, we need to set it GREATER THAN blueLabel's height priority
adjustableLabelHeightConstraint.priority = UILayoutPriority(rawValue: blue50.priority.rawValue + 1)
// activate those constraints
NSLayoutConstraint.activate([blueLabelTop, blue10, blue50, adjustableLabelHeightConstraint])
// add a tap gesture recognizer so we can increas the height of the label
let t = UITapGestureRecognizer(target: self, action: #selector(self.gotTap(_:)))
view.addGestureRecognizer(t)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
viewSafeAreaHeight = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom)
updateLabelText()
}
@objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
adjustableLabelHeightConstraint.constant += 50
updateLabelText()
}
func updateLabelText() -> Void {
let blueHeight = blueLabel.frame.height
let redHeight = redLabel.frame.height
let redConstant = adjustableLabelHeightConstraint.constant
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
percentFormatter.minimumFractionDigits = 2
percentFormatter.maximumFractionDigits = 2
guard let bluePct = percentFormatter.string(for: blueHeight / viewSafeAreaHeight) else { return }
var s = "SafeArea Height: \(viewSafeAreaHeight)"
s += "\n"
s += "Blue Height: \(blueHeight)"
s += "\n"
s += "\(blueHeight) / \(viewSafeAreaHeight) = \(bluePct)"
blueLabel.text = s
s = "Tap to increase..."
s += "\n"
s += "Red Height Constant: \(redConstant)"
s += "\n"
s += "Red Actual Height: \(redHeight)"
redLabel.text = s
}
}