哪里写Autolayout布局最合适?
哪里写Autolayout布局最合适?
hejunbinlan 发表于1年前
哪里写Autolayout布局最合适?
  • 发表于 1年前
  • 阅读 17
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 哪里写Autolayout布局最合适?

用户需求一览

申请者fengmingxiao

项目大致代码行数500

项目 GitHub 地址

项目备注

探究UI布局方面的代码应该怎么写才是规范的,希望您能够 看看Readme,万分感谢,这不是什么大工程,但是我目前最棘手的问题

在哪里写Autolayout布局最合适?

在回答这个问题前,我们先看一看UIView这个类的头文件里有哪些和布局相关的方法.

// Allows you to perform layout before the drawing cycle happens. -layoutIfNeeded forces layout early
    public func setNeedsLayout()
    public func layoutIfNeeded()

    public func layoutSubviews()

相信如果从纯frame布局时代过来的人应该对这三个方法比较熟悉.
说一个场景.如图,我创建了一个自定义的黑色View,里面包含一个红色的view,一个黄色的view.
Button的事件,是将黑色View的宽高都扩大一倍.
我点击完Button之后.效果如下.

你们会发现,黄色视图的宽高发生了变化,而红色的没有.为什么呢?
因为我在创建黑色视图的时候重写了layoutSubviews()方法.

override func layoutSubviews() {
        yellowView.frame = CGRectInset(self.bounds, 20, 20)
    }

也就是说,layoutSubViews总是会在父Viewframe发生变化的时候触发.(不止这一个场景会被触发.)
列举一下. layoutSubviews在以下情况下会被调用: 1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转Screen会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
7、直接调用setLayoutSubviews。
所以如果子view的宽高与父view的宽高相关的话,就需要这样写.当然,这是远古时代的写法了.现在我们如果使用Autolayout的话,就不需要关心这些了.
那么我最上面贴的三个方法中的前两个是干吗的呢?
解释如下.
setNeedsLayout()方法: 标记为需要重新布局,异步调用layoutIfNeeded()刷新布局,不立即刷新,但layoutSubviews()一定会被调用 layoutIfNeeded()方法:如果,有需要刷新的标记,立即调用layoutSubviews()进行布局(如果没有标记,不会调用layoutSubviews()

如果要立即刷新,要先调用view.setNeedsLayout(),把标记设为需要布局,然后马上调用view.layoutIfNeeded(),实现布局

在视图第一次显示之前,标记总是“需要刷新”的,可以直接调用view.layoutIfNeeded()

好的讲完了frame布局.下面来讲一下现在最常使用的Autolayout布局.

Autolayout

在UIView的头文件中,Autolayout相关的方法一般有下面几个.

 public func updateConstraintsIfNeeded() // Updates the constraints from the bottom up for the view hierarchy rooted at the receiver. UIWindow's implementation creates a layout engine if necessary first.
    @available(iOS 6.0, *)
    public func updateConstraints() // Override this to adjust your special constraints during a constraints update pass
    @available(iOS 6.0, *)
    public func needsUpdateConstraints() -> Bool
    @available(iOS 6.0, *)
    public func setNeedsUpdateConstraints()
}

其实关于这几个方法的解释,苹果官方文档已经解释的很清楚了.

needsUpdateConstraints() -> Bool这个方法返回一个Bool值,来决定一个view是否会执行updateConstraints()这个方法.

updateConstraints()方法一般会重写,用来更新特定的constraint(这个方法使用较复杂,下面细讲).

updateConstraintsIfNeeded()方法和setNeedsUpdateConstraints()方法的含义和上述的public func setNeedsLayout()public func layoutIfNeeded()类似.只不过一个是针对frame布局更新.,一个是针对Autolayout的布局更新. setNeedsUpdateConstraints()方法会在每次view中布局发生变化的时候都会触发,提醒view应当更新了.相当于是设置一个标记.这个方法调用的时候,布局变化并不会立刻生效,只是提醒系统.
updateConstraintsIfNeeded()这个方法调用的时候,会立刻强制刷新被标记为需要刷新的布局.

为什么说updateConstraints()使用起来特别纠结?

解释如下.

Custom views that set up constraints themselves should do so by overriding this method. When your custom view notes that a change has been made to the view that invalidates one of its constraints, it should immediately remove that constraint, and then call setNeedsUpdateConstraints to note that constraints need to be updated. Before layout is performed, your implementation of updateConstraints will be invoked, allowing you to verify that all necessary constraints for your content are in place at a time when your custom view’s properties are not changing.

这是苹果官网给出的解释.
意思是:当视图的改变使得某个约束无效时,应当把该约束移除,并调用 setNeedsUpdateConstraints 以标记视图需要更新约束,然后在 updateConstraints 中更新约束。

但是实际上使用起来几乎等同于扯淡.

why?
每次执行 updateConstraints() 方法时,视图的状态并不是完全相同的。视图可能已经有一些约束了,而且大部分情况下你可能只想更改一部分约束。这就导致 updateConstraints() 中散落着各种if语句用来判断指定的约束是否已经存在。 视图也不一定完全拥有它的所有约束。视图层级中的其他视图可能会把约束加到你的视图上,所以你的代码也不能假设constraints数组中的哪个约束是干什么的。这意味着你必须用单独的属性来跟踪所有可能需要修改的约束。 改变约束通常是在响应事件的时候。如果你遵循了官方文档的建议,在处理事件,改变内部状态后,就需要调用 setNeedsUpdateConstraints() 。 结果就是:修改布局的代码(在updateConstraints() 里面)和触发修改的代码分离在不同的地方,逻辑难以理解。

##说了那么多,到底应该怎么做?
在今年的WWDC技术讲座 Mysteries of Auto Layout (Part 2) 上,苹果给出了不同的建议:

Really, all this is is a way for views to have a chance to make changes to constraints just in time for the next layout pass, but it’s often not actually needed.
All of your initial constraint setup should ideally happen inside Interface Builder. Or if you really find that you need to allocate your constraints programmatically, some place like viewDidLoad is much better. updateConstraints is really just for work that needs to be repeated periodically.
Also, it’s pretty straightforward to just change constraints when you find the need to do that; whereas, if you take that logic apart from the other code that’s related to it and you move it into a separate method that gets executed at a later time, your code becomes a lot harder to follow, so it will be harder for you to maintain, it will be a lot harder for other people to understand.
So when would you need to use updateConstraints? Well, it boils down to performance. If you find that just changing your constraints in place is too slow, then update constraints might be able to help you out. It turns out that changing a constraint inside updateConstraints is actually faster than changing a constraint at other times. The reason for that is because the engine is able to treat all the constraint changes that happen in this pass as a batch.

简单来说.不要将 updateConstraints() 用于视图的初始化设置。当你需要在单个布局流程(single layout pass)中添加、修改或删除大量约束的时候,用它来获得最佳性能。如果没有性能问题,直接更新约束更简单。

最后一句话总结.
在哪里创建autolayout.
View中:直接在init方法里创建.
ViewController中:直接在viewDidLoad()里创建.
有人问,如果用IB创建约束,在viewDidLoad里不能获取到某个view的正确frame,怎么办?
这个时候你需要在一个叫viewDidLayoutSubviews()里的方法里获取一个view的正确frame.

参考文章

  1. 產生 Auto Layout Constraints 的程式碼要放在哪裡
  2. What is the difference between all these Auto Layout update methods? Are all necessary?
  3. Objective-C setNeedsUpdateConstraints vs. updateConstraintsIfNeeded
共有 人打赏支持
粉丝 38
博文 528
码字总数 21018
×
hejunbinlan
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: