【iOS 开发】从 setNeedsLayout 说起

简介: 本文从 setNeedsLayout 这个方法说起,分享与其相关的 UIKit 视图交互、使用场景等内容。UIKit 为 UIView 提供了这些方法来进行视图的更新与重绘:public func setNeedsLayout()public f...

本文从 <code>setNeedsLayout</code> 这个方法说起,分享与其相关的 UIKit 视图交互、使用场景等内容。

UIKit 为 UIView 提供了这些方法来进行视图的更新与重绘:

public func setNeedsLayout()
public func layoutSubviews()
public func layoutIfNeeded()

public func setNeedsDisplay()
public func setNeedsDisplayInRect(rect: CGRect)
public func drawRect(rect: CGRect)

运行时视图交互模型

无论是用户交互触发还是代码自动触发,下图展示的事件序列都同样适用,这里用到了 <code>setNeedsLayout</code> 方法:

UIKit interactions with your view objects

上图对应的事件序列如下:

  1. 用户触摸屏幕
  2. 硬件报告触摸事件给 UIKit 框架
  3. UIKit 框架将触摸事件打包成 UIEvent 对象,然后分发给合适的视图
  4. 事件处理代码会对相应事件作出响应,代码可以是这样的:
    -更改 <code>frame</code>、<code>bounds</code>、<code>alpha</code> 等属性
    -调用 <code>setNeedsLayout</code> 方法以标记该视图(或者它的子视图)为需要进行布局更新
    -调用 <code>setNeedsDisplay</code> 或者 <code>setNeedsDisplayInRect: </code> 方法以标记该视图(或者它的子视图)需要进行重画
    -通知 Controller 有数据变化
  5. 如果一个视图的几何结构改变了,UIKit 会更新它的子视图
  6. 如果任何视图的任何部分被标记为需要重画,UIKit 会要求视图重画自身
  7. 任何已经更新的视图会与应用余下的可视内容组合在一起,同时被发送到图形硬件去显示
  8. 图形硬件将已解释内容转化到屏幕上

方法调用逻辑

在上面的过程中,我们可以接触到文章开头提到的方法,他们的调用逻辑是这样的:

<code>setNeedsLayout</code> 会给当前 UIView 立一个 flag,以表示后续应该调用 <code>layoutSubviews</code> 方法,以调整当前视图及其子视图的布局。

<code>setNeedsDisplayInRect: </code> 会给当前 UIView 立一个 flag,以表示后续应该调用 <code>drawRect:</code> 方法,以进行视图重绘。


View Drawing Cycle

Apple 官方文档已经明确说明,开发者不应该直接调用 <code>layoutSubviews</code> 与 <code>drawRect:</code> ,而应该在你认为系统默认的布局和重绘不能带给你想要的效果时,在子类中重写这些方法,然后分别通过 <code>setNeedsLayout</code> 和 <code>setNeedsDisplayInRect: </code> 来进行调用。

当然你可以给多个 UIView 设置 <code>setNeedsLayout</code>,然后当下一个 View Drawing Cycle 到来时,多个 UIView 的视图会一同更改布局。

那么这个 View Drawing Cycle 到底是什么呢,官方是这样解释的:

The system waits until the end of the current run loop before initiating any drawing operations. This delay gives you a chance to invalidate multiple views, add or remove views from your hierarchy, hide views, resize views, and reposition views all at once. All of the changes you make are then reflected at the same time.

显然这样用 RunLoop 把多次修改聚集在一个 Cycle 一并进行渲染是更加高效的行为。

(我个人对 View Drawing Cycle 的理解是这样的:UIKit 需要处理非常多的事件,这些事件组合起来变成了一个非常复杂的事件序列,在这个序列中有些特定的点是 UIKit 专门提供给 UIView 来进行视图更改的。如上所述,在当前 run loop 结束之前,我们有机会做各种视图更改,并且这些更改会在下一个 run loop 体现出来,所以** View Drawing Cycle 就是一次次 run loop 中我们通过 UIKit 得到的 UIView 重布局、重绘机会所组成的循环**。有理解不对的地方,欢迎评论指正。)


如何善用 View Drawing Cycle

一个很常见的例子是,一个 iPad App,横屏和竖屏时界面布局不一样,那么你可以监听设备旋转,在设备旋转时执行 <code>setNeedsLayout</code> 方法,然后在 <code>layoutSubviews</code> 里面通过判断接下来是横屏还是竖屏来进行不一样的布局设置。基本上你不可能只在这个方法里只进行了单个 UIView 的布局修改,而是多项修改,那么 App 会在下一个 View Drawing Cycle 到来时,把这些修改一起执行,这是最正常的情况。

那么假如我不按 Apple 规定的来,直接调用 <code>layoutSubviews</code> 呢?我们可以猜想一下:因为这个方法里面提供了我们需要的布局方式,所以 UIView 会按我们想要的方式来布局,但是因为各种视图修改的请求时机是零碎的,所以这样效率会低一些。所以重要的其实是了解何时会触发 <code>layoutSubviews</code>:

  • init 初始化不会触发 layoutSubviews
  • addSubview 会触发 layoutSubviews
  • 设置 view 的 frame 会触发 layoutSubviews,当然前提是 frame 的值设置前后发生了变化
  • 滚动一个 UIScrollView 会触发 layoutSubviews
  • 旋转 Screen 会触发父 UIView 上的 layoutSubviews 事件
  • 改变一个 UIView 大小的时候也会触发父 UIView 上的 layoutSubviews 事件

然后按 Apple 要求的方式来做就好了(分别通过 <code>setNeedsLayout</code> 和 <code>setNeedsDisplayInRect: </code> 来调用 <code>layoutSubviews</code> 和 <code>drawRect:</code>)

但有些情况比较特殊:你打开 iOS 的时钟应用,去看里面的秒表页面,这个页面里面的两个按钮是没有 UIButton 默认的动画的,点击之后,按钮会瞬间改变自身的状态(颜色、内部 Label 的内容),这种情况我们需要跳出 View Drawing Cycle,来实现一个瞬间改变的效果。实现方法如下:

extension UIButton {
    func quickButtonAction() {
        UIView.performWithoutAnimation({
            // do something
            self.layoutIfNeeded()
        })
    }
}

可以看出 <code>layoutIfNeeded</code> 作为一个辅助选项给了 <code>setNeedsLayout</code> 一个可以瞬时执行的特点。当然默认这个“选项”是关闭的。


setNeedsDisplay 补充

<code>setNeedsLayout</code> 的使用场景之前已经提过了(iPad App),下面举个栗子说一下 <code>setNeedsDisplayInRect: </code>的使用场景。

假如我需要在两点之间绘制一条直线,有两个 <code>dotView</code>,需要绘制一个 <code>lineView</code>。我在 <code>drawRect:</code> 方法里实现了 <code>lineView</code> 的具体绘制方法(根据两个点来绘制)。那么如果我想要这个直线一直根据两个点同步变化的话,就需要在 <code>dotView</code> 的位置发生改变时,执行:

lineView.setNeedsDisplay() // 重绘 lineView

至于 <code>drawRect:</code> 方法什么时候会被触发:

From StackOverFlow

一个很好的参考链接:What is the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews?

目录
相关文章
|
9天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
117 1
|
2月前
|
设计模式 安全 Swift
探索iOS开发:打造你的第一个天气应用
【9月更文挑战第36天】在这篇文章中,我们将一起踏上iOS开发的旅程,从零开始构建一个简单的天气应用。文章将通过通俗易懂的语言,引导你理解iOS开发的基本概念,掌握Swift语言的核心语法,并逐步实现一个具有实际功能的天气应用。我们将遵循“学中做,做中学”的原则,让理论知识和实践操作紧密结合,确保学习过程既高效又有趣。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你打开一扇通往iOS开发世界的大门。
|
2月前
|
搜索推荐 IDE API
打造个性化天气应用:iOS开发之旅
【9月更文挑战第35天】在这篇文章中,我们将一起踏上iOS开发的旅程,通过创建一个个性化的天气应用来探索Swift编程语言的魅力和iOS平台的强大功能。无论你是编程新手还是希望扩展你的技能集,这个项目都将为你提供实战经验,帮助你理解从构思到实现一个应用的全过程。让我们开始吧,构建你自己的天气应用,探索更多可能!
66 1
|
2天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
16天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
37 9
|
13天前
|
设计模式 Swift iOS开发
探索iOS开发:从基础到高级,打造你的第一款App
【10月更文挑战第40天】在这个数字时代,掌握移动应用开发已成为许多技术爱好者的梦想。本文将带你走进iOS开发的世界,从最基础的概念出发,逐步深入到高级功能实现,最终指导你完成自己的第一款App。无论你是编程新手还是有志于扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。让我们一起开始这段旅程吧!
|
16天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
14天前
|
iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第39天】在苹果的生态系统中,SwiftUI框架以其声明式语法和易用性成为开发者的新宠。本文将深入SwiftUI的核心概念,通过实际案例展示如何利用这一框架快速构建用户界面,并探讨其对iOS应用开发流程的影响。
|
17天前
|
JSON 前端开发 API
探索iOS开发之旅:打造你的第一个天气应用
【10月更文挑战第36天】在这篇文章中,我们将踏上一段激动人心的旅程,一起构建属于我们自己的iOS天气应用。通过这个实战项目,你将学习到如何从零开始搭建一个iOS应用,掌握基本的用户界面设计、网络请求处理以及数据解析等核心技能。无论你是编程新手还是希望扩展你的iOS开发技能,这个项目都将为你提供宝贵的实践经验。准备好了吗?让我们开始吧!