更复杂的 AutoLayout 方程
More complicated AutoLayout equations
您好,请看下面的模型:
我想知道如何从上面创建约束:
V2.top = C1.top + n * V1.height
因为这与约束的 default equation 不同:
item1.attribute1 = multiplier × item2.attribute2 + constant
我知道我可以只使用 AutoResizingMask
但它会在我的代码中造成真正的混乱,因为我的代码非常复杂,而且我也不那么喜欢 AutoResizingMask
。
(顺便说一句,请只回答Swift
!)
谢谢
使用辅助视图可以解决。
在这种情况下,辅助视图只是用于调整大小的 UIView
,没有自己的可见内容。设置其 alpha = 0 或 hidden = true。
- 设置helperView.top = c1.top
- 设置helperView.height = v1.height
- 设置v2.top = helperView.bottom + 5
您还需要为助手视图设置宽度和行距,但它们的值并不重要。
您可以使用 UILayoutGuide
执行此操作 -- 来自 Apple 的 docs:
The UILayoutGuide class is designed to perform all the tasks previously performed by dummy views, but to do it in a safer, more efficient manner.
为了获得您想要的布局,我们可以:
- 添加布局指南
C1
- 将其顶部限制为
C1
顶部
- 使用“n”乘数
将其高度限制为V1
高度
- 将
V2
顶部限制到指南的底部
这里有一个完整的例子来演示:
class GuideViewController: UIViewController {
// a label on each side so we can
// "tap to change" v1 Height and "n" multiplier
let labelN = UILabel()
let labelH = UILabel()
let containerView = UIView()
let v1 = UILabel()
let v2 = UILabel()
// a layout guide for v2's Top spacing
let layG = UILayoutGuide()
// we'll change these on taps
var n:CGFloat = 0
var v1H: CGFloat = 30
// constraints we'll want to modify when "n" or "v1H" change
var v1HeightConstraint: NSLayoutConstraint!
var layGHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
v1.text = "V1"
v2.text = "V2"
v1.textAlignment = .center
v2.textAlignment = .center
containerView.backgroundColor = .systemTeal
v1.backgroundColor = .green
v2.backgroundColor = .yellow
[containerView, v1, v2].forEach {
[=10=].translatesAutoresizingMaskIntoConstraints = false
}
containerView.addSubview(v1)
containerView.addSubview(v2)
view.addSubview(containerView)
// add the layout guide to containerView
containerView.addLayoutGuide(layG)
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's give the container 80-pts Top/Bottom and 120-pts on each side
containerView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 80.0),
containerView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 120.0),
containerView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -120.0),
containerView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -80.0),
// v1 Leading / Trailing / Bottom 20-pts
v1.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
v1.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
v1.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
// just use v2's intrinisic height
// v2 Leading / Trailing 20-pts
v2.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
v2.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
// layout Guide Top / Leading / Trailing
layG.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
layG.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0),
layG.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0),
// and constrain v2 Top to layout Guide Bottom
v2.topAnchor.constraint(equalTo: layG.bottomAnchor, constant: 0.0),
])
// layout Guide Height equals v1 Height x n
layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
layGHeightConstraint.isActive = true
// v1 Height
v1HeightConstraint = v1.heightAnchor.constraint(equalToConstant: v1H)
v1HeightConstraint.isActive = true
// "tap to change" labels
[labelN, labelH].forEach {
[=10=].translatesAutoresizingMaskIntoConstraints = false
[=10=].backgroundColor = UIColor(white: 0.9, alpha: 1.0)
[=10=].textAlignment = .center
[=10=].numberOfLines = 0
view.addSubview([=10=])
let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
[=10=].addGestureRecognizer(t)
[=10=].isUserInteractionEnabled = true
}
NSLayoutConstraint.activate([
labelN.topAnchor.constraint(equalTo: containerView.topAnchor),
labelN.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
labelN.trailingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: -8.0),
labelN.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
labelH.topAnchor.constraint(equalTo: containerView.topAnchor),
labelH.leadingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 8.0),
labelH.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
labelH.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
updateInfo()
}
@objc func tapHandler(_ gr: UITapGestureRecognizer) -> Void {
guard let v = gr.view else {
return
}
// if we tapped on the "cylcle N" label
if v == labelN {
n += 1
if n == 6 {
n = 0
}
// can't change multiplier directly, so
// de-Activate / set it / Activate
layGHeightConstraint.isActive = false
layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
layGHeightConstraint.isActive = true
}
// if we tapped on the "cylcle v1H" label
if v == labelH {
v1H += 5
if v1H > 50 {
v1H = 30
}
v1HeightConstraint.constant = v1H
}
updateInfo()
}
func updateInfo() -> Void {
var s: String = ""
s = "Tap to cycle \"n\" from Zero to 5\n\nn = \(n)"
labelN.text = s
s = "Tap to cycle \"v1H\" from 30 to 50\n\nv1H = \(v1H)"
labelH.text = s
}
}
当你运行它时,它看起来像这样:
每次点击左侧,它都会将 n
乘数变量从零循环到 5,并更新约束条件。
每次点击右侧,v1H
高度变量会从 30 循环到 50,并更新约束条件。
您好,请看下面的模型:
我想知道如何从上面创建约束:
V2.top = C1.top + n * V1.height
因为这与约束的 default equation 不同:
item1.attribute1 = multiplier × item2.attribute2 + constant
我知道我可以只使用 AutoResizingMask
但它会在我的代码中造成真正的混乱,因为我的代码非常复杂,而且我也不那么喜欢 AutoResizingMask
。
(顺便说一句,请只回答Swift
!)
谢谢
使用辅助视图可以解决。
在这种情况下,辅助视图只是用于调整大小的 UIView
,没有自己的可见内容。设置其 alpha = 0 或 hidden = true。
- 设置helperView.top = c1.top
- 设置helperView.height = v1.height
- 设置v2.top = helperView.bottom + 5
您还需要为助手视图设置宽度和行距,但它们的值并不重要。
您可以使用 UILayoutGuide
执行此操作 -- 来自 Apple 的 docs:
The UILayoutGuide class is designed to perform all the tasks previously performed by dummy views, but to do it in a safer, more efficient manner.
为了获得您想要的布局,我们可以:
- 添加布局指南
C1
- 将其顶部限制为
C1
顶部 - 使用“n”乘数 将其高度限制为
- 将
V2
顶部限制到指南的底部
V1
高度
这里有一个完整的例子来演示:
class GuideViewController: UIViewController {
// a label on each side so we can
// "tap to change" v1 Height and "n" multiplier
let labelN = UILabel()
let labelH = UILabel()
let containerView = UIView()
let v1 = UILabel()
let v2 = UILabel()
// a layout guide for v2's Top spacing
let layG = UILayoutGuide()
// we'll change these on taps
var n:CGFloat = 0
var v1H: CGFloat = 30
// constraints we'll want to modify when "n" or "v1H" change
var v1HeightConstraint: NSLayoutConstraint!
var layGHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
v1.text = "V1"
v2.text = "V2"
v1.textAlignment = .center
v2.textAlignment = .center
containerView.backgroundColor = .systemTeal
v1.backgroundColor = .green
v2.backgroundColor = .yellow
[containerView, v1, v2].forEach {
[=10=].translatesAutoresizingMaskIntoConstraints = false
}
containerView.addSubview(v1)
containerView.addSubview(v2)
view.addSubview(containerView)
// add the layout guide to containerView
containerView.addLayoutGuide(layG)
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's give the container 80-pts Top/Bottom and 120-pts on each side
containerView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 80.0),
containerView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 120.0),
containerView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -120.0),
containerView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -80.0),
// v1 Leading / Trailing / Bottom 20-pts
v1.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
v1.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
v1.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
// just use v2's intrinisic height
// v2 Leading / Trailing 20-pts
v2.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
v2.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
// layout Guide Top / Leading / Trailing
layG.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
layG.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0),
layG.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0),
// and constrain v2 Top to layout Guide Bottom
v2.topAnchor.constraint(equalTo: layG.bottomAnchor, constant: 0.0),
])
// layout Guide Height equals v1 Height x n
layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
layGHeightConstraint.isActive = true
// v1 Height
v1HeightConstraint = v1.heightAnchor.constraint(equalToConstant: v1H)
v1HeightConstraint.isActive = true
// "tap to change" labels
[labelN, labelH].forEach {
[=10=].translatesAutoresizingMaskIntoConstraints = false
[=10=].backgroundColor = UIColor(white: 0.9, alpha: 1.0)
[=10=].textAlignment = .center
[=10=].numberOfLines = 0
view.addSubview([=10=])
let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
[=10=].addGestureRecognizer(t)
[=10=].isUserInteractionEnabled = true
}
NSLayoutConstraint.activate([
labelN.topAnchor.constraint(equalTo: containerView.topAnchor),
labelN.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
labelN.trailingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: -8.0),
labelN.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
labelH.topAnchor.constraint(equalTo: containerView.topAnchor),
labelH.leadingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 8.0),
labelH.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
labelH.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
updateInfo()
}
@objc func tapHandler(_ gr: UITapGestureRecognizer) -> Void {
guard let v = gr.view else {
return
}
// if we tapped on the "cylcle N" label
if v == labelN {
n += 1
if n == 6 {
n = 0
}
// can't change multiplier directly, so
// de-Activate / set it / Activate
layGHeightConstraint.isActive = false
layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
layGHeightConstraint.isActive = true
}
// if we tapped on the "cylcle v1H" label
if v == labelH {
v1H += 5
if v1H > 50 {
v1H = 30
}
v1HeightConstraint.constant = v1H
}
updateInfo()
}
func updateInfo() -> Void {
var s: String = ""
s = "Tap to cycle \"n\" from Zero to 5\n\nn = \(n)"
labelN.text = s
s = "Tap to cycle \"v1H\" from 30 to 50\n\nv1H = \(v1H)"
labelH.text = s
}
}
当你运行它时,它看起来像这样:
每次点击左侧,它都会将 n
乘数变量从零循环到 5,并更新约束条件。
每次点击右侧,v1H
高度变量会从 30 循环到 50,并更新约束条件。