我正在阅读有关自动布局渲染管道的信息,这意味着自动布局如何工作。有一些方法可以在autoLayout渲染的不同阶段调用,如
layoutIfNeeded()
layoutSubviews()
updateConstraints()
updateConstraintsIfNeeded()
但我不知道哪个方法被调用的时间和方法的意义是什么,如果我想使用自动布局,那么我可以使用该方法以及如何控制autoLayout渲染管道
答案 0 :(得分:1)
通常您不需要关心autolayout方法链。您只需要为视图创建约束以定义其大小和位置。您可以在视图的生命周期中随时添加/删除,激活/停用约束,但是您希望始终拥有一组可满足(非冲突)但完整的约束。
举个例子。您可以告诉自动布局按钮A
应该是50点宽,20点高,其左上角位于viewController
视图中的点(0,0)。现在,这是按钮A
的非冲突但完整的约束集。但是,当用户点击它时,可以说你想扩展该按钮。所以在点击处理程序中你会添加一个新的约束,说按钮应该是100点宽 - 现在你有不可满足的约束 - 有一个约束说它应该是50点宽,另一个说它应该是100点宽。因此,为了防止冲突,在激活新约束之前,必须停用旧约束。不完整的约束是相反的情况,假设您停用旧的宽度约束,但从不激活新的约束。然后autolayout可以计算位置(因为有定义它的约束),高度,但不是宽度,通常以未定义的行为结束(现在在UIButton
的情况下不是真的,因为它具有内在大小,这是隐含的定义它的宽度和高度,但我希望你明白这一点。
因此,当您创建这些约束时,由您决定(在我的示例中,当用户点击按钮时您正在操纵它们)。通常,如果是UIView
子类,或者loadView
子类中的UIViewController
,则可以在初始化程序中启动,您可以在其中定义和激活默认的约束集。然后,您可以使用处理程序来响应用户活动。我的建议是准备loadView
中的所有约束,将它们保存在属性中,并在必要时激活/停用它们。
但是当然还有一些限制因为何时以及如何不创建新的约束 - 有关这些案例的更详细讨论我真的建议通过objc.io查看Advanced Autolayout Toolbox。
修改强>
请参阅以下示例,该示例使用autolayout进行布局的简单自定义SongView
,并通过激活/停用约束来支持约束中的一些动态更改。您只需将整个代码粘贴到游乐场并在那里进行测试,或将其包含在项目中即可。
请注意,我没有调用任何自动布局生命周期方法,setNeedsLayout
和layoutIfNeeded
除外。 setNeedsLayout
设置一个标志告诉autolayout已经更改了约束,然后layoutIfNeeded
告诉它重新计算帧。通常情况下,这会自动发生,但要为我们需要明确告知它的约束更改设置动画 - 请参阅setExpanded
中的SongView
方法。有关在动画中使用自动布局的更详细说明,请参阅my different answer。
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let songView = SongView()
let button = UIButton()
override func loadView() {
super.loadView()
view.backgroundColor = .white
self.view.addSubview(button)
self.view.addSubview(songView)
button.setTitle("Expand/Collapse", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(expandCollapse), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
songView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// button has intrinsic size, no need to define constraints for size, position is enough
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -50),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
// songView has defined its height (see SongView class), but not width, therefore we need more constraints
songView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
songView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
songView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
])
}
@objc func expandCollapse() {
if songView.isExpanded {
songView.setExpanded(to: false, animated: true)
} else {
songView.setExpanded(to: true, animated: true)
}
}
}
class SongView: UIView {
private let numberLabel: UILabel = UILabel()
private let nameLabel: UILabel = UILabel()
private var expandedConstraints: [NSLayoutConstraint] = []
private var collapsedConstraints: [NSLayoutConstraint] = []
// this can be triggered by some event
private(set) var isExpanded: Bool = false
func setExpanded(to expanded: Bool, animated: Bool) {
self.isExpanded = expanded
if animated {
if expanded {
// setup expanded state
NSLayoutConstraint.deactivate(collapsedConstraints)
NSLayoutConstraint.activate(expandedConstraints)
} else {
// setup collapsed
NSLayoutConstraint.deactivate(expandedConstraints)
NSLayoutConstraint.activate(collapsedConstraints)
}
self.setNeedsLayout()
UIView.animate(withDuration: 0.2, animations: {
self.layoutIfNeeded()
})
} else {
// non animated version (no need to explicitly call setNeedsLayout nor layoutIfNeeded)
if expanded {
// setup expanded state
NSLayoutConstraint.deactivate(collapsedConstraints)
NSLayoutConstraint.activate(expandedConstraints)
} else {
// setup collapsed
NSLayoutConstraint.deactivate(expandedConstraints)
NSLayoutConstraint.activate(collapsedConstraints)
}
}
}
var data: (String, String)? {
didSet {
numberLabel.text = data?.0
nameLabel.text = data?.1
}
}
init() {
super.init(frame: CGRect.zero)
setupInitialHierarchy()
setupInitialAttributes()
setupInitialLayout()
}
fileprivate func setupInitialHierarchy() {
self.addSubview(numberLabel)
self.addSubview(nameLabel)
}
fileprivate func setupInitialAttributes() {
numberLabel.font = UIFont.boldSystemFont(ofSize: UIFont.preferredFont(forTextStyle: UIFontTextStyle.body).pointSize)
numberLabel.textColor = UIColor.darkGray
numberLabel.text = "0"
numberLabel.textAlignment = .right
nameLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
nameLabel.text = "NONE"
nameLabel.textAlignment = .left
self.backgroundColor = UIColor.lightGray
}
fileprivate func setupInitialLayout() {
self.translatesAutoresizingMaskIntoConstraints = false
numberLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.translatesAutoresizingMaskIntoConstraints = false
// just randomly selected different layouts for collapsed and expanded states
expandedConstraints = [
numberLabel.widthAnchor.constraint(equalToConstant: 35),
self.heightAnchor.constraint(equalToConstant: 80),
]
collapsedConstraints = [
numberLabel.widthAnchor.constraint(equalToConstant: 50),
self.heightAnchor.constraint(equalToConstant: 40),
]
// activating collapsed as default layout
NSLayoutConstraint.activate(collapsedConstraints)
NSLayoutConstraint.activate([
numberLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 4),
numberLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4),
numberLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 4),
nameLabel.centerYAnchor.constraint(equalTo: numberLabel.centerYAnchor),
nameLabel.leftAnchor.constraint(equalTo: numberLabel.rightAnchor, constant: 8),
nameLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -4)
])
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
PlaygroundPage.current.liveView = ViewController()