layoutSubviews、setNeedsLayout、setNeedsDisplay、drawRect区别及调用的时机

本文详细介绍了iOS自动布局的工作原理,包括layoutSubviews、setNeedsLayout、layoutIfNeeded等方法的作用及调用时机,以及基于约束的AutoLayer的方法如setNeedsUpdateConstraints、updateConstraintsIfNeeded等,并解释了自动布局过程中的三个关键步骤:updatingconstraints、layout和display。

最近开始学习自动布局,难免就会涉及到这些常见的问题,咱们开门见山,直接了当的介绍一下

首先 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。
 主要是以上的几个方法中


layoutSubviews, 当我们在某个类的内部调整子视图位置时,需要调用。

反过来的意思就是说:如果你想要在外部设置subviews的位置,就不要重写。

刷新子对象布局
    -setNeedsLayout方法: 标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定会被调用
    -layoutIfNeeded方法: 如果,有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)

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

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

重绘


关于UIView的Layer,IOS提供了三个方法:

1、layoutSubviews

在iOS5.1和之前的版本,此方法的缺省实现不会做任何事情(实现为空),iOS5.1之后(iOS6开始)的版本,此方法的缺省实现是使用你设置在此view上面的constraints(Autolayout)去决定subviews的position和size。 UIView的子类如果需要对其subviews进行更精确的布局,则可以重写此方法。只有在autoresizingconstraint-based behaviors of subviews不能提供我们想要的布局结果的时候,我们才应该重写此方法。可以在此方法中直接设置subviews的frame。 我们不应该直接调用此方法,而应当用下面两个方法。

2、setNeedsLayout

此方法会将view当前的layout设置为无效的,并在下一个upadte cycle里去触发layout更新。

3、layoutIfNeeded

使用此方法强制立即进行layout,从当前view开始,此方法会遍历整个view层次(包括superviews)请求layout。因此,调用此方法会强制整个view层次布局。


基于约束的AutoLayer的方法:

1、setNeedsUpdateConstraints

当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后调用updateConstraints.

2、needsUpdateConstraints

constraint-based layout system使用此返回值去决定是否需要调用updateConstraints作为正常布局过程的一部分。

3、updateConstraintsIfNeeded

立即触发约束更新,自动更新布局。

4、updateConstraints

自定义view应该重写此方法在其中建立constraints. 注意:要在实现在最后调用[super updateConstraints]

Auto Layout Process 自动布局过程

与使用springs and struts(autoresizingMask)比较,Auto layout在view显示之前,多引入了两个步骤:updating constraints 和laying out views。每一个步骤都依赖于上一个。display依赖layout,而layout依赖updating constraints。 updating constraints->layout->display

第一步:updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。

自定义view的话,通常可以重写updateConstraints方法,在其中可以添加view需要的局部的contraints。

第二步:layout,其从上向下(from super view to subview),此步主要应用上一步的信息去设置view的center和bounds。可以通过调用setNeedsLayout去触发此步骤,此方法不会立即应用layout。如果想要系统立即的更新layout,可以调用layoutIfNeeded。另外,自定义view可以重写方法layoutSubViews来在layout的工程中得到更多的定制化效果。

第三步:display,此步时把view渲染到屏幕上,它与你是否使用Auto layout无关,其操作是从上向下(from super view to subview),通过调用setNeedsDisplay触发,

因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。

需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。

注意:如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。 

如下图:


### ### 多次调用 `layoutSubviews` 的影响和后果 在 iOS 开发中,`layoutSubviews` 是一个非常关键的方法,用于在视图的布局发生改变时重新计算和设置子视图的位置和大小。它通常在视图的生命周期中被多次调用,例如在添加子视图、修改视图的 `frame` 或者设备方向改变时。然而,频繁调用 `layoutSubviews` 可能会对性能和布局逻辑产生一定的影响。 #### 1. **性能影响** `layoutSubviews` 的频繁调用可能导致布局计算的重复执行,尤其是在包含大量子视图或复杂布局逻辑的情况下。例如,在添加多个子视图时,父视图的 `layoutSubviews` 会触发一次,而不是多次,但子视图的 `layoutSubviews` 也会被调用,从而增加整体的计算负担[^1]。如果在 `layoutSubviews` 中执行复杂的布局计算或动画操作,可能会导致主线程阻塞,进而影响应用的响应速度。 #### 2. **布局逻辑的不一致性** 如果在 `layoutSubviews` 中执行了与视图状态相关的逻辑,例如根据某些条件动态调整子视图的位置或大小,那么多次调用可能导致布局状态的不一致。例如,在 `viewDidLoad` 中添加子视图并设置约束时,`layoutSubviews` 会被调用一次,此时可以安全地执行布局逻辑。但如果在其他情况下(如设备方向改变)再次调用 `layoutSubviews`,而没有正确处理状态变化,可能会导致子视图的位置或大小不符合预期[^3]。 #### 3. **设备方向改变时的额外调用** 在 iPhone 设备上,当设备方向发生改变时,`layoutSubviews` 会被调用两次,这可能是由于系统内部的布局更新机制导致的[^4]。如果在 `layoutSubviews` 中执行了某些副作用操作(如网络请求或数据更新),可能会导致不必要的资源消耗。因此,在实现 `layoutSubviews` 时,应尽量避免执行耗时操作,或者在执行检查是否真的需要更新布局。 #### 4. **与 `setNeedsLayout` 和 `layoutIfNeeded` 的交互** `setNeedsLayout` 会标记视图需要重新布局,而 `layoutIfNeeded` 会立即触发布局更新。如果在短时间内多次调用 `setNeedsLayout` 或 `layoutIfNeeded`,可能会导致 `layoutSubviews` 被多次调用,从而增加性能开销。为了优化性能,可以在布局逻辑中使用 `layoutIfNeeded` 来确保布局立即更新,而不是等待系统的布局周期[^2]。 #### 5. **调试和测试的复杂性** 由于 `layoutSubviews` 可能在多个不同的上下文中被调用,调试和测试其行为可能会变得更加复杂。例如,在调试设备方向变化时,可能会发现 `layoutSubviews` 被调用了多次,而这些调用可能来自不同的源(如父视图的 `frame` 变化或子视图的 `frame` 变化)。为了简化调试过程,可以在 `layoutSubviews` 中添加日志输出,记录调用堆栈,以便追踪调用来源。 ### ### 示例代码 以下是一个简单的示例,展示了如何在 `layoutSubviews` 中避免重复执行布局逻辑: ```objective-c - (void)layoutSubviews { [super layoutSubviews]; // 避免重复执行布局逻辑 if (CGRectEqualToRect(self.bounds, self.lastLayoutBounds)) { return; } // 执行布局逻辑 for (UIView *subview in self.subviews) { // 根据新的 bounds 调整子视图的布局 subview.frame = CGRectMake(0, 0, self.bounds.size.width / 2, self.bounds.size.height / 2); } // 记录当的 bounds self.lastLayoutBounds = self.bounds; } ``` 在这个示例中,通过比较当的 `bounds` 和上一次布局时的 `bounds`,可以避免不必要的布局计算,从而减少性能开销。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值