NSLayoutConstraint 高度不起作用
NSLayoutConstraint height is not working
我已使用编程约束将视图定义为 iOS 屏幕上的弹出窗口。
let stopTimer = StoppageTimer(frame: CGRect.zero)
视图本身包含一个堆栈视图,外加几个按钮。当我尝试为我的视图设置约束时(从它的超级视图 - 一个视图控制器),除了我的视图的高度之外,所有这些都被正确应用。设置这些约束的代码是(违规集是最后四个,就在 view.layoutIfNeeded()
之前
func setConstraints() {
// Remove all constraints within the UIView
view.constraints.forEach {constraint in constraint.isActive = false}
lblNetScore.translatesAutoresizingMaskIntoConstraints = false
lblMatchName.translatesAutoresizingMaskIntoConstraints = false
butUnwind.translatesAutoresizingMaskIntoConstraints = false
butMatchStats.translatesAutoresizingMaskIntoConstraints = false
GSButtons.translatesAutoresizingMaskIntoConstraints = false
GAButtons.translatesAutoresizingMaskIntoConstraints = false
sb.translatesAutoresizingMaskIntoConstraints = false
timer.translatesAutoresizingMaskIntoConstraints = false
butSwitch.translatesAutoresizingMaskIntoConstraints = false
Qtr.translatesAutoresizingMaskIntoConstraints = false
butStart.translatesAutoresizingMaskIntoConstraints = false
stopTimer.translatesAutoresizingMaskIntoConstraints = false
// Top Line
NSLayoutConstraint(item: butUnwind, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 15).isActive = true
NSLayoutConstraint(item: butUnwind, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: lblNetScore, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: lblNetScore, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butMatchStats, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -15).isActive = true
NSLayoutConstraint(item: butMatchStats, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: lblMatchName, attribute: .top, relatedBy: .equal, toItem: lblNetScore, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: lblMatchName, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
// Timer
NSLayoutConstraint(item: timer, attribute: .top, relatedBy: .equal, toItem: lblMatchName, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: timer, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: Qtr, attribute: .top, relatedBy: .equal, toItem: lblMatchName, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: Qtr, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: Qtr, attribute: .height, relatedBy: .equal, toItem: timer, attribute: .height, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butStart, attribute: .top, relatedBy: .equal, toItem: lblMatchName, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: butStart, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butStart, attribute: .height, relatedBy: .equal, toItem: timer, attribute: .height, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butStart, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 70).isActive = true
// Switch Button
NSLayoutConstraint(item: butSwitch, attribute: .top, relatedBy: .equal, toItem: timer, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: butSwitch, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
// ScoreBoard
NSLayoutConstraint(item: sb, attribute: .top, relatedBy: .equal, toItem: butSwitch, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: sb, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
//Scoring buttons - GS
NSLayoutConstraint(item: GSButtons, attribute: .top, relatedBy: .equal, toItem: sb, attribute: .bottom, multiplier: 1, constant: 7).isActive = true
NSLayoutConstraint(item: GSButtons, attribute: .height, relatedBy: .equal, toItem: sb, attribute: .height, multiplier: 1, constant: 15).isActive = true
NSLayoutConstraint(item: GSButtons, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: GSButtons, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin,multiplier: 1, constant: 0).isActive = true
// Scoring buttons - GA
NSLayoutConstraint(item: GAButtons, attribute: .top, relatedBy: .equal, toItem: GSButtons, attribute: .bottom, multiplier: 1, constant: 7).isActive = true
NSLayoutConstraint(item: GAButtons, attribute: .height, relatedBy: .equal, toItem: sb, attribute: .height, multiplier: 1, constant: 15).isActive = true
NSLayoutConstraint(item: GAButtons, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: GAButtons, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
// Stoppage Timer
NSLayoutConstraint(item: stopTimer, attribute: .top, relatedBy: .equal, toItem: butSwitch, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
view.layoutIfNeeded()
}
因此视图位于另一个按钮下方,并且top/leading/trailing约束是完美的,但是高度被忽略了(调试中没有约束错误window)。当我在调试中查看高度值时,它告诉我它为零
(lldb) po stopTimer.frame
▿ (16.0, 186.5, 343.0, 0.0)
▿ origin : (16.0, 186.5)
- x : 16.0
- y : 186.5
▿ size : (343.0, 0.0)
- width : 343.0
- height : 0.0
我使用 CGRect.zero
预先声明视图,因为我的约束稍后会调整大小。
如果我将高度设置为与另一个视图相等,它可以正常工作,但它不会将其设置为恒定高度。如果我尝试以类似的方式使用宽度约束,同样的事情也会发生。
如能帮助解开这个谜团,我们将不胜感激。
编辑
当 stopTimer 视图出现时(我设置 .isHidden = false),子视图中的控件(按钮、堆栈视图等)都显示在屏幕上,但无法访问(我无法触摸它们),因为它们不在视图范围内。抱歉冗长,但这里是 stopTimer class 定义
class StoppageTimer: UIView {
lazy var StoppageType: UISegmentedControl = {
let s = UISegmentedControl(frame: CGRect.zero)
s.insertSegment(withTitle: "Umpire Time", at: 0, animated: false)
s.insertSegment(withTitle: "Injury Time", at: 1, animated: false)
s.translatesAutoresizingMaskIntoConstraints = false
s.backgroundColor = Style.backgroundColor
s.tintColor = Style.buttonBackgroundColorA
return s
}()
lazy var StoppageTimer: UIStackView = {
let s = UIStackView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
s.axis = .horizontal
s.distribution = .fill
s.alignment = .fill
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
let bgView: UIView = {
let v = UIView()
v.backgroundColor = Style.labelBackgroundColorA
v.layer.cornerRadius = CGFloat(Style.buttonCornerRadius)
v.layer.borderWidth = 3
v.layer.borderColor = Style.buttonBackgroundColorA.cgColor
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let minutes: UILabel = {
let l = UILabel()
l.text = "00"
l.textAlignment = .right
l.backgroundColor = UIColor.clear
l.textColor = Style.labelTextColor
l.font = UIFont.systemFont(ofSize: 40.0, weight: .thin)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let Separator: UILabel = {
let l = UILabel()
l.text = ":"
l.textAlignment = .center
l.backgroundColor = UIColor.clear
l.textColor = Style.labelTextColor
l.font = UIFont.systemFont(ofSize: 40.0, weight: .ultraLight)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let seconds: UILabel = {
let l = UILabel()
l.text = "00"
l.textAlignment = .left
l.backgroundColor = UIColor.clear
l.textColor = Style.labelTextColor
l.font = UIFont.systemFont(ofSize: 40.0, weight: .thin)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let butCont: UIButton = {
let b = UIButton()
b.setTitle("Continue", for: .normal)
b.setTitleColor(Style.buttonTextColor, for: .normal)
b.titleLabel?.font = UIFont.systemFont(ofSize: 25)
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.showsTouchWhenHighlighted = true
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = Style.buttonBackgroundColorB
b.layer.cornerRadius = CGFloat(Style.buttonCornerRadius)
b.layer.borderWidth = CGFloat(Style.buttonBorderWidth)
return b
}()
override init(frame: CGRect) {
super.init(frame: frame)
addStoppageTimer()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setStoppageTimerConstraints()
}
func addStoppageTimer() {
StoppageTimer.arrangedSubviews.forEach { subview in subview.removeFromSuperview() }
addSubview(bgView)
StoppageTimer.addArrangedSubview(minutes)
StoppageTimer.addArrangedSubview(Separator)
StoppageTimer.addArrangedSubview(seconds)
addSubview(StoppageTimer)
addSubview(StoppageType)
addSubview(butCont)
}
func setStoppageTimerConstraints() {
constraints.forEach { constraint in constraint.isActive = false }
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: bgView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 20).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -20).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .top, relatedBy: .equal, toItem: StoppageType, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 150).isActive = true
NSLayoutConstraint(item: butCont, attribute: .centerX, relatedBy: .equal, toItem: bgView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butCont, attribute: .top, relatedBy: .equal, toItem: StoppageTimer, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
minutes.widthAnchor.constraint(equalToConstant: 60).isActive = true
seconds.widthAnchor.constraint(equalToConstant: 60).isActive = true
layoutIfNeeded()
}
我看不出所有其他约束都能完美工作的任何原因(即使高度确实是指另一个视图的高度,而不仅仅是一个常量值),但高度和宽度在定义为时被忽略一个常数。调试日志完全无声,不反对任何约束。
我还注意到,在调试时,高度约束是在执行高度约束线时设置的,但是在view.layoutIfNeeded()
之后查看约束,高度约束不再...
(lldb) po stopTimer.constraints
▿ 1 element
- 0 : <NSLayoutConstraint:0x6000000997d0 NetScore.StoppageTimer:0x7fc3bff223d0.height == 100 (active)>
(lldb) po stopTimer.constraints
▿ 11 elements
- 0 : <NSLayoutConstraint:0x60c00009d6f0 V:|-(0)-[UIView:0x7fc3bff225f0] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 1 : <NSLayoutConstraint:0x60c000281090 UIView:0x7fc3bff225f0.bottom == NetScore.StoppageTimer:0x7fc3bff223d0.bottom (active)>
- 2 : <NSLayoutConstraint:0x60c0002810e0 H:|-(0)-[UIView:0x7fc3bff225f0] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 3 : <NSLayoutConstraint:0x60c000281130 UIView:0x7fc3bff225f0.trailing == NetScore.StoppageTimer:0x7fc3bff223d0.trailing (active)>
- 4 : <NSLayoutConstraint:0x60c000281180 V:|-(10)-[UISegmentedControl:0x7fc3bff23f10] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 5 : <NSLayoutConstraint:0x60c0002811d0 H:|-(20)-[UISegmentedControl:0x7fc3bff23f10] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 6 : <NSLayoutConstraint:0x60c000281220 UISegmentedControl:0x7fc3bff23f10.trailing == NetScore.StoppageTimer:0x7fc3bff223d0.trailing - 20 (active)>
- 7 : <NSLayoutConstraint:0x60c0002812c0 V:[UISegmentedControl:0x7fc3bff23f10]-(0)-[UIStackView:0x7fc3bff23d00] (active)>
- 8 : <NSLayoutConstraint:0x60c000281310 UIStackView:0x7fc3bff23d00.centerX == NetScore.StoppageTimer:0x7fc3bff223d0.centerX (active)>
- 9 : <NSLayoutConstraint:0x60c00009f360 UIButton:0x7fc3bff23080'Continue'.centerX == UIView:0x7fc3bff225f0.centerX (active)>
- 10 : <NSLayoutConstraint:0x60c0002813b0 V:[UIStackView:0x7fc3bff23d00]-(5)-[UIButton:0x7fc3bff23080'Continue'] (active)>
据我所知,您遇到的问题实际上与高度限制无关。
您需要添加这一行:
stopTimer.translatesAutoresizingMaskIntoConstraints = false
问题是,此 属性 的默认值为 true
,这意味着视图将仅具有基于视图框架自动生成的一组约束。您之后添加的任何约束都将不起作用。 false
表示不依赖授权掩码,想自己配置约束。
希望对您有所帮助!
在 setStoppageTimerConstraints()
中,您说的是:
NSLayoutConstraint(item: bgView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
将 bgView
固定到所有四个边(因此它应该完全填满 StoppageTimer
视图)。
然后...
NSLayoutConstraint(item: StoppageType, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 20).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -20).isActive = true
固定 StoppageType
(分段控件)前缘和后缘,并从视图的 Top
固定其 Top
10 点。
那么……
NSLayoutConstraint(item: StoppageTimer, attribute: .top, relatedBy: .equal, toItem: StoppageType, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 150).isActive = true
固定 StoppageTimer
(堆栈视图)前缘和后缘,并从 StoppageType
的 Bottom
固定其 Top
0 点。
然后...
NSLayoutConstraint(item: butCont, attribute: .centerX, relatedBy: .equal, toItem: bgView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butCont, attribute: .top, relatedBy: .equal, toItem: StoppageTimer, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
固定 butCont
(一个按钮)centerX,并从 StoppageTimer.
的 Bottom
固定其 Top
5 点
到目前为止,还不错。但是...您忘记添加约束来控制 view
本身的 Height
。
所以,添加这一行:
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: butCont, attribute: .bottom, multiplier: 1.0, constant: 10.0).isActive = true
这表示 view Bottom
应该等于 butCont
+ 10 分的 Bottom
。
现在您可以将 stopTimer
添加到 VC 的视图中,您只需要设置其前导、尾随和顶部约束。 stopTimer
的 content 的约束将定义其高度。
编辑: 说明为什么在原始代码中设置高度约束不起作用...
在您的 VC 中 setConstraints()
的末尾,您正在这样做:
// Stoppage Timer
NSLayoutConstraint(item: stopTimer, attribute: .top, relatedBy: .equal, toItem: butSwitch, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
设置顶部、前导和尾随约束和高度约束。
在您的 StoppageTimer
视图中,您实施了 traitCollectionDidChange()
来添加/更新您的约束(它调用 setStoppageTimerConstraints()
)。在 setStoppageTimerConstraints()
的开头,您 删除 它的所有约束。这似乎没问题,除了...
stopTimer
视图的顶部、前导和尾随约束 属于 您的 VC 视图,而stopTimer
视图的 Height
约束属于 stopTimer.view
.
traitCollectionDidChange()
被调用不止一次。事实上,在您设置了高度约束后 ,它会被调用 。所以:
constraints.forEach { constraint in constraint.isActive = false }
从 VC.
中删除 刚刚设置的高度限制
希望这是有道理的。
我已使用编程约束将视图定义为 iOS 屏幕上的弹出窗口。
let stopTimer = StoppageTimer(frame: CGRect.zero)
视图本身包含一个堆栈视图,外加几个按钮。当我尝试为我的视图设置约束时(从它的超级视图 - 一个视图控制器),除了我的视图的高度之外,所有这些都被正确应用。设置这些约束的代码是(违规集是最后四个,就在 view.layoutIfNeeded()
func setConstraints() {
// Remove all constraints within the UIView
view.constraints.forEach {constraint in constraint.isActive = false}
lblNetScore.translatesAutoresizingMaskIntoConstraints = false
lblMatchName.translatesAutoresizingMaskIntoConstraints = false
butUnwind.translatesAutoresizingMaskIntoConstraints = false
butMatchStats.translatesAutoresizingMaskIntoConstraints = false
GSButtons.translatesAutoresizingMaskIntoConstraints = false
GAButtons.translatesAutoresizingMaskIntoConstraints = false
sb.translatesAutoresizingMaskIntoConstraints = false
timer.translatesAutoresizingMaskIntoConstraints = false
butSwitch.translatesAutoresizingMaskIntoConstraints = false
Qtr.translatesAutoresizingMaskIntoConstraints = false
butStart.translatesAutoresizingMaskIntoConstraints = false
stopTimer.translatesAutoresizingMaskIntoConstraints = false
// Top Line
NSLayoutConstraint(item: butUnwind, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 15).isActive = true
NSLayoutConstraint(item: butUnwind, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: lblNetScore, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: lblNetScore, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butMatchStats, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -15).isActive = true
NSLayoutConstraint(item: butMatchStats, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: lblMatchName, attribute: .top, relatedBy: .equal, toItem: lblNetScore, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: lblMatchName, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
// Timer
NSLayoutConstraint(item: timer, attribute: .top, relatedBy: .equal, toItem: lblMatchName, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: timer, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: Qtr, attribute: .top, relatedBy: .equal, toItem: lblMatchName, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: Qtr, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: Qtr, attribute: .height, relatedBy: .equal, toItem: timer, attribute: .height, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butStart, attribute: .top, relatedBy: .equal, toItem: lblMatchName, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: butStart, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butStart, attribute: .height, relatedBy: .equal, toItem: timer, attribute: .height, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butStart, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 70).isActive = true
// Switch Button
NSLayoutConstraint(item: butSwitch, attribute: .top, relatedBy: .equal, toItem: timer, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: butSwitch, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
// ScoreBoard
NSLayoutConstraint(item: sb, attribute: .top, relatedBy: .equal, toItem: butSwitch, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
NSLayoutConstraint(item: sb, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
//Scoring buttons - GS
NSLayoutConstraint(item: GSButtons, attribute: .top, relatedBy: .equal, toItem: sb, attribute: .bottom, multiplier: 1, constant: 7).isActive = true
NSLayoutConstraint(item: GSButtons, attribute: .height, relatedBy: .equal, toItem: sb, attribute: .height, multiplier: 1, constant: 15).isActive = true
NSLayoutConstraint(item: GSButtons, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: GSButtons, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin,multiplier: 1, constant: 0).isActive = true
// Scoring buttons - GA
NSLayoutConstraint(item: GAButtons, attribute: .top, relatedBy: .equal, toItem: GSButtons, attribute: .bottom, multiplier: 1, constant: 7).isActive = true
NSLayoutConstraint(item: GAButtons, attribute: .height, relatedBy: .equal, toItem: sb, attribute: .height, multiplier: 1, constant: 15).isActive = true
NSLayoutConstraint(item: GAButtons, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: GAButtons, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
// Stoppage Timer
NSLayoutConstraint(item: stopTimer, attribute: .top, relatedBy: .equal, toItem: butSwitch, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
view.layoutIfNeeded()
}
因此视图位于另一个按钮下方,并且top/leading/trailing约束是完美的,但是高度被忽略了(调试中没有约束错误window)。当我在调试中查看高度值时,它告诉我它为零
(lldb) po stopTimer.frame
▿ (16.0, 186.5, 343.0, 0.0)
▿ origin : (16.0, 186.5)
- x : 16.0
- y : 186.5
▿ size : (343.0, 0.0)
- width : 343.0
- height : 0.0
我使用 CGRect.zero
预先声明视图,因为我的约束稍后会调整大小。
如果我将高度设置为与另一个视图相等,它可以正常工作,但它不会将其设置为恒定高度。如果我尝试以类似的方式使用宽度约束,同样的事情也会发生。
如能帮助解开这个谜团,我们将不胜感激。
编辑
当 stopTimer 视图出现时(我设置 .isHidden = false),子视图中的控件(按钮、堆栈视图等)都显示在屏幕上,但无法访问(我无法触摸它们),因为它们不在视图范围内。抱歉冗长,但这里是 stopTimer class 定义
class StoppageTimer: UIView {
lazy var StoppageType: UISegmentedControl = {
let s = UISegmentedControl(frame: CGRect.zero)
s.insertSegment(withTitle: "Umpire Time", at: 0, animated: false)
s.insertSegment(withTitle: "Injury Time", at: 1, animated: false)
s.translatesAutoresizingMaskIntoConstraints = false
s.backgroundColor = Style.backgroundColor
s.tintColor = Style.buttonBackgroundColorA
return s
}()
lazy var StoppageTimer: UIStackView = {
let s = UIStackView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
s.axis = .horizontal
s.distribution = .fill
s.alignment = .fill
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
let bgView: UIView = {
let v = UIView()
v.backgroundColor = Style.labelBackgroundColorA
v.layer.cornerRadius = CGFloat(Style.buttonCornerRadius)
v.layer.borderWidth = 3
v.layer.borderColor = Style.buttonBackgroundColorA.cgColor
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let minutes: UILabel = {
let l = UILabel()
l.text = "00"
l.textAlignment = .right
l.backgroundColor = UIColor.clear
l.textColor = Style.labelTextColor
l.font = UIFont.systemFont(ofSize: 40.0, weight: .thin)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let Separator: UILabel = {
let l = UILabel()
l.text = ":"
l.textAlignment = .center
l.backgroundColor = UIColor.clear
l.textColor = Style.labelTextColor
l.font = UIFont.systemFont(ofSize: 40.0, weight: .ultraLight)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let seconds: UILabel = {
let l = UILabel()
l.text = "00"
l.textAlignment = .left
l.backgroundColor = UIColor.clear
l.textColor = Style.labelTextColor
l.font = UIFont.systemFont(ofSize: 40.0, weight: .thin)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let butCont: UIButton = {
let b = UIButton()
b.setTitle("Continue", for: .normal)
b.setTitleColor(Style.buttonTextColor, for: .normal)
b.titleLabel?.font = UIFont.systemFont(ofSize: 25)
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.showsTouchWhenHighlighted = true
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = Style.buttonBackgroundColorB
b.layer.cornerRadius = CGFloat(Style.buttonCornerRadius)
b.layer.borderWidth = CGFloat(Style.buttonBorderWidth)
return b
}()
override init(frame: CGRect) {
super.init(frame: frame)
addStoppageTimer()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setStoppageTimerConstraints()
}
func addStoppageTimer() {
StoppageTimer.arrangedSubviews.forEach { subview in subview.removeFromSuperview() }
addSubview(bgView)
StoppageTimer.addArrangedSubview(minutes)
StoppageTimer.addArrangedSubview(Separator)
StoppageTimer.addArrangedSubview(seconds)
addSubview(StoppageTimer)
addSubview(StoppageType)
addSubview(butCont)
}
func setStoppageTimerConstraints() {
constraints.forEach { constraint in constraint.isActive = false }
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: bgView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 20).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -20).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .top, relatedBy: .equal, toItem: StoppageType, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 150).isActive = true
NSLayoutConstraint(item: butCont, attribute: .centerX, relatedBy: .equal, toItem: bgView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butCont, attribute: .top, relatedBy: .equal, toItem: StoppageTimer, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
minutes.widthAnchor.constraint(equalToConstant: 60).isActive = true
seconds.widthAnchor.constraint(equalToConstant: 60).isActive = true
layoutIfNeeded()
}
我看不出所有其他约束都能完美工作的任何原因(即使高度确实是指另一个视图的高度,而不仅仅是一个常量值),但高度和宽度在定义为时被忽略一个常数。调试日志完全无声,不反对任何约束。
我还注意到,在调试时,高度约束是在执行高度约束线时设置的,但是在view.layoutIfNeeded()
之后查看约束,高度约束不再...
(lldb) po stopTimer.constraints
▿ 1 element
- 0 : <NSLayoutConstraint:0x6000000997d0 NetScore.StoppageTimer:0x7fc3bff223d0.height == 100 (active)>
(lldb) po stopTimer.constraints
▿ 11 elements
- 0 : <NSLayoutConstraint:0x60c00009d6f0 V:|-(0)-[UIView:0x7fc3bff225f0] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 1 : <NSLayoutConstraint:0x60c000281090 UIView:0x7fc3bff225f0.bottom == NetScore.StoppageTimer:0x7fc3bff223d0.bottom (active)>
- 2 : <NSLayoutConstraint:0x60c0002810e0 H:|-(0)-[UIView:0x7fc3bff225f0] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 3 : <NSLayoutConstraint:0x60c000281130 UIView:0x7fc3bff225f0.trailing == NetScore.StoppageTimer:0x7fc3bff223d0.trailing (active)>
- 4 : <NSLayoutConstraint:0x60c000281180 V:|-(10)-[UISegmentedControl:0x7fc3bff23f10] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 5 : <NSLayoutConstraint:0x60c0002811d0 H:|-(20)-[UISegmentedControl:0x7fc3bff23f10] (active, names: '|':NetScore.StoppageTimer:0x7fc3bff223d0 )>
- 6 : <NSLayoutConstraint:0x60c000281220 UISegmentedControl:0x7fc3bff23f10.trailing == NetScore.StoppageTimer:0x7fc3bff223d0.trailing - 20 (active)>
- 7 : <NSLayoutConstraint:0x60c0002812c0 V:[UISegmentedControl:0x7fc3bff23f10]-(0)-[UIStackView:0x7fc3bff23d00] (active)>
- 8 : <NSLayoutConstraint:0x60c000281310 UIStackView:0x7fc3bff23d00.centerX == NetScore.StoppageTimer:0x7fc3bff223d0.centerX (active)>
- 9 : <NSLayoutConstraint:0x60c00009f360 UIButton:0x7fc3bff23080'Continue'.centerX == UIView:0x7fc3bff225f0.centerX (active)>
- 10 : <NSLayoutConstraint:0x60c0002813b0 V:[UIStackView:0x7fc3bff23d00]-(5)-[UIButton:0x7fc3bff23080'Continue'] (active)>
据我所知,您遇到的问题实际上与高度限制无关。
您需要添加这一行:
stopTimer.translatesAutoresizingMaskIntoConstraints = false
问题是,此 属性 的默认值为 true
,这意味着视图将仅具有基于视图框架自动生成的一组约束。您之后添加的任何约束都将不起作用。 false
表示不依赖授权掩码,想自己配置约束。
希望对您有所帮助!
在 setStoppageTimerConstraints()
中,您说的是:
NSLayoutConstraint(item: bgView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: bgView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
将 bgView
固定到所有四个边(因此它应该完全填满 StoppageTimer
视图)。
然后...
NSLayoutConstraint(item: StoppageType, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 20).isActive = true
NSLayoutConstraint(item: StoppageType, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -20).isActive = true
固定 StoppageType
(分段控件)前缘和后缘,并从视图的 Top
固定其 Top
10 点。
那么……
NSLayoutConstraint(item: StoppageTimer, attribute: .top, relatedBy: .equal, toItem: StoppageType, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: StoppageTimer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 150).isActive = true
固定 StoppageTimer
(堆栈视图)前缘和后缘,并从 StoppageType
的 Bottom
固定其 Top
0 点。
然后...
NSLayoutConstraint(item: butCont, attribute: .centerX, relatedBy: .equal, toItem: bgView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: butCont, attribute: .top, relatedBy: .equal, toItem: StoppageTimer, attribute: .bottom, multiplier: 1, constant: 5).isActive = true
固定 butCont
(一个按钮)centerX,并从 StoppageTimer.
Bottom
固定其 Top
5 点
到目前为止,还不错。但是...您忘记添加约束来控制 view
本身的 Height
。
所以,添加这一行:
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: butCont, attribute: .bottom, multiplier: 1.0, constant: 10.0).isActive = true
这表示 view Bottom
应该等于 butCont
+ 10 分的 Bottom
。
现在您可以将 stopTimer
添加到 VC 的视图中,您只需要设置其前导、尾随和顶部约束。 stopTimer
的 content 的约束将定义其高度。
编辑: 说明为什么在原始代码中设置高度约束不起作用...
在您的 VC 中 setConstraints()
的末尾,您正在这样做:
// Stoppage Timer
NSLayoutConstraint(item: stopTimer, attribute: .top, relatedBy: .equal, toItem: butSwitch, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: stopTimer, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1, constant: 0).isActive = true
设置顶部、前导和尾随约束和高度约束。
在您的 StoppageTimer
视图中,您实施了 traitCollectionDidChange()
来添加/更新您的约束(它调用 setStoppageTimerConstraints()
)。在 setStoppageTimerConstraints()
的开头,您 删除 它的所有约束。这似乎没问题,除了...
stopTimer
视图的顶部、前导和尾随约束 属于 您的 VC 视图,而stopTimer
视图的 Height
约束属于 stopTimer.view
.
traitCollectionDidChange()
被调用不止一次。事实上,在您设置了高度约束后 ,它会被调用 。所以:
constraints.forEach { constraint in constraint.isActive = false }
从 VC.
中删除 刚刚设置的高度限制希望这是有道理的。