【问题标题】:NSLayoutConstraint height is not workingNSLayoutConstraint 高度不起作用
【发布时间】:2018-09-11 11:55:42
【问题描述】:

我已使用编程约束将视图定义为 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()
}

因此视图位于另一个按钮下方,并且顶部/前导/尾随约束是完美的,但高度被忽略(调试窗口中没有约束错误)。当我在调试中查看高度值时,它告诉我它为零

(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 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内容视图决定它的高度吗?

标签: ios swift nslayoutconstraint


【解决方案1】:

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-pts。

那么……

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(堆栈视图)前沿和后沿,并从StoppageTypeBottom 固定其Top 0-pts。

那么……

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-pts

到目前为止,一切都很好。但是...您忘记添加一个约束来控制view 本身的Height

所以,添加这一行:

NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: butCont, attribute: .bottom, multiplier: 1.0, constant: 10.0).isActive = true

这表示view Bottom 应该等于butContBottom + 10-pts。

现在您可以将stopTimer 添加到您的VC 视图中,您只需要设置它的前导、尾随和顶部约束。 stopTimercontent 的约束将定义其高度。


编辑:澄清为什么在原始代码中设置高度约束不起作用...

在您的 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 中设置的高度约束。

希望这是有道理的。

【讨论】:

  • 谢谢。有效!我没有定义视图的底部是有道理的。我仍然有点困惑,为什么我不能定义一个固定高度的视图,而不必将它的底部引用到其他控件,但我会在安静的时刻思考一下。目前,这对我有用。感谢您的帮助。
  • @Jonno - 请参阅我编辑的答案以进行澄清(另外,为了他人的利益,请务必将答案标记为已接受)。
【解决方案2】:

据我所知,您面临的问题实际上与高度限制无关。

你需要添加这一行:

stopTimer.translatesAutoresizingMaskIntoConstraints = false

问题是,该属性的默认值为true,这意味着视图将仅具有基于视图框架自动生成的一组约束。并且您之后添加的任何约束都将不起作用。 false 表示你不依赖授权掩码,你想自己配置约束。

希望对你有帮助!

【讨论】:

  • 哦,对不起。在我看到您将此属性设置为 false 的更新问题之前发布了此问题。
猜你喜欢
  • 1970-01-01
  • 2017-05-15
  • 1970-01-01
  • 2012-01-02
  • 1970-01-01
  • 1970-01-01
  • 2013-10-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多