程序约束未按预期工作
Posted
技术标签:
【中文标题】程序约束未按预期工作【英文标题】:Programmatic Constraints not working as expected 【发布时间】:2017-03-04 06:11:10 【问题描述】:我花了几个小时试图找出阻止我的约束布局工作的原因。我有一个名为 ABSegment
的视图,它只包含一个 UILabel,它应该居中并具有与其父视图相同的高度和宽度。
我将包含此 UIView 子视图的整个类定义以显示我所做的。
import UIKit
class ABSegment: UIView
let titleLabel = UILabel()
init(withTitle title: String)
titleLabel.text = title
titleLabel.textAlignment = .center
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(titleLabel)
backgroundColor = UIColor.blue
override func layoutSubviews()
super.layoutSubviews()
logFrames()
func logFrames()
print("self.frame is \(frame)")
print("titleLabel.frame is \(titleLabel.frame)")
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func updateConstraints()
logFrames()
titleLabel.removeConstraints(titleLabel.constraints)
let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: titleLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
let height = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([centerX, centerY, width, height])
super.updateConstraints()
很自然地怀疑我没有用 initWithFrame 初始化这个视图。相反,我将框架设置推迟到构建这些视图的代码的后面。所以构造这些的代码不会设置框架。框架可以在 Storyboard 中设置或像这样:
override func viewDidLayoutSubviews()
super.viewDidLayoutSubviews()
let frame = CGRect(x: view.bounds.origin.x + 150, y: view.bounds.origin.y + 200, width: 100, height: 100)
segment.frame = frame
segment.layoutSubviews()
我的理解是,由于我正在调用segment.layoutSubviews()
,因此 ABSegment 视图应该进行更改以将先前激活的约束应用于最终帧。
有很多不同的设置和事情要按照正确的顺序正确处理,除了根本看不到标签出现之外,没有任何反馈。
【问题讨论】:
【参考方案1】:我看到的代码的主要问题:
每次调用 updateConstraints
时,您都在添加和删除约束。您只需在创建视图时设置一次约束。
您将 translatesAutoresizingMaskIntoConstraints = false
设置在 ABSegment
本身上。不要这样做。这告诉自动布局,您将使用约束来指定ABSegment
的大小和位置,并且显然您为此目的使用了frame
。
这是重构后的代码:
class ABSegment: UIView
let titleLabel = UILabel()
// Computed property to allow title to be changed
var title: String
set
titleLabel.text = newValue
get
return titleLabel.text ?? ""
override init(frame: CGRect)
super.init(frame: frame)
setupLabel(title: "")
convenience init(title: String)
self.init(frame: CGRect.zero)
self.title = title
// This init is called if your view is
// set up in the Storyboard
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
setupLabel(title: "")
func setupLabel(title: String)
titleLabel.text = title
titleLabel.textAlignment = .center
addSubview(titleLabel)
backgroundColor = UIColor.blue
titleLabel.translatesAutoresizingMaskIntoConstraints = false
let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: titleLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
let height = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([centerX, centerY, width, height])
override func layoutSubviews()
super.layoutSubviews()
logFrames()
func logFrames()
print("self.frame is \(frame)")
print("titleLabel.frame is \(titleLabel.frame)")
override func updateConstraints()
super.updateConstraints()
logFrames()
注意事项:
-
我将标签的所有设置移到了一个名为
setupLabel
的函数中。这允许它被两个初始化器调用。
我向ABSegment
添加了一个名为title
的计算属性,它允许您随时使用mysegment.title = "new title"
更改title
。
我把init(withSegment:)
变成了一个方便的初始化。它调用标准init(frame:)
,然后设置title
。使用withProperty
不是一种常见的做法,所以我改变了它。您将使用var segment = ABSegment(title: "some title")
创建一个ABSegment
。
我让required init?(coder aDecoder: NSCoder)
调用setupLabel
,以便ABSegment
可以与故事板中添加的视图一起使用。
layoutSubviews
和 updateConstraints
的覆盖被保留以记录帧。这就是他们现在所做的一切。
【讨论】:
太棒了!实际上,将自动调整大小的蒙版设置为约束是罪魁祸首。我也很欣赏您提供的其他编码技巧,以及在构造标签时只需要设置一次约束的知识。我认为应该在每个“updateConstraints”重新计算约束的部分原因是我将它与layoutSubviews
进行了比较。对于 layoutSubviews,我觉得值得为每个布局通道完成计算帧。
层次结构中的根视图可能会发生变化,然后所有后代视图框架都需要在 layoutSubviews 时计算新的矩形。随着约束旋转,我不确定这是否也需要。因此,您只需要在该视图的初始时间定义/激活约束。所以约束不共享相同的语义。以上是关于程序约束未按预期工作的主要内容,如果未能解决你的问题,请参考以下文章