在渲染周期

时间:2018-01-12 07:13:44

标签: ios swift autolayout ios-autolayout

我正在阅读有关自动布局渲染管道的信息,这意味着自动布局如何工作。有一些方法可以在autoLayout渲染的不同阶段调用,如

  • layoutIfNeeded()
  • layoutSubviews()
  • updateConstraints()
  • updateConstraintsIfNeeded()

但我不知道哪个方法被调用的时间和方法的意义是什么,如果我想使用自动布局,那么我可以使用该方法以及如何控制autoLayout渲染管道

1 个答案:

答案 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,并通过激活/停用约束来支持约束中的一些动态更改。您只需将整个代码粘贴到游乐场并在那里进行测试,或将其包含在项目中即可。

请注意,我没有调用任何自动布局生命周期方法,setNeedsLayoutlayoutIfNeeded除外。 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()