onAppear( task )是 SwiftUI 开发者经常运用的一个修饰符,但一向没有威望的文档清晰它的闭包被调用的机遇。本文将经过 SwiftUI 4 供给的新 API ,证明 onAppear 的调用机遇是在布局之后、烘托之前。
原文发表在我的博客wwww.fatbobman.com
欢迎订阅我的大众号:【肘子的Swift记事本】
问题
同之前多篇博客相似,咱们仍是从 聊天室 的一个 问题 开始。
请忽略比如中的写法是否合理和值得推荐,仅考虑为什么在榜首段代码中,呈现了数组越界的状况;以及第二段代码能够正确运转。
创立实例、求值、布局、烘托
在 SwiftUI 中,一个视图在它的生命周期中通常会经历四个阶段:
创立实例
视图树中,处于可显现分支的视图基本上都会经历的一个阶段。在一个视图的生计期中,SwiftUI 可能会多次创立视图实例。
因为惰性视图的优化机制,对于尚未处于可见区域的子视图,SwiftUI 不会创立其实例
求值
一个被显现的视图至少会经历一次的进程。因为 SwiftUI 的视图实际上是一个函数,SwiftUI 需求对视图进行求值( 调用 body 特点 )并保留核算成果。当视图的依靠( Source of truth )发生变化后,SwiftUI 会从头核算视图成果值,并与旧值进行比较。如发生变化,则用新值替换旧值。
布局
在核算好当时需求显现的视图所有的视图值后,SwiftUI 将进入到布局阶段。经过父视图向子视图供给建议尺度,子视图回来需求尺度这一进程,最终核算出完好的布局成果。
有关布局的流程请阅览 SwiftUI 布局 —— 尺度
烘托
SwiftUI 经过调用愈加底层的 API,将视图在屏幕上呈现的进程。此进程严厉意义上已经不属于 SwiftUI 的办理范畴了。
Appear 是相对于谁的?
在不少的词典中,appear 都被解释为例如 to come into sight; become visible
这样的意思。这会让开发者误以为 onAppear 是在视图烘托后( 运用者看到后 )才被调用的。但在 SwiftUI 中,onAppear 实际上是在烘托前被调用的。
假定排除了苹果起名呈现了过错这个原因,此时的 appear 更像是针对 SwiftUI 系统来说的。视图在完成了创立实例、求值、布局后( 完成了属于 SwiftUI 架构的办理流程 ),就算是 appear 于 SwiftUI 的“眼前”。
求证
口说无凭,本节咱们将用依据来证明上述揣度。
在写 SwiftUI 视图的生命周期研讨 一文时,咱们只能经过现象来揣度 onAppear 的调用机遇,随着版本的不断提高,SwiftUI 4 中为咱们供给了足够的东西让咱们能够取得愈加确实的依据。
判别视图正在求值
在视图中添加相似如下的代码,是咱们判别 SwiftUI 是否正在对视图进行求值的常用手段:
VStack {
let _ = print("evaluate")
}
判别视图正处于布局阶段
在 4.0 中版本中,SwiftUI 供给了 Layout 协议,答应咱们创立自定义布局容器,经过创立符合该协议的实例,咱们便能够判别当时视图是否正处于布局阶段。
struct MyLayout: Layout {
let name: String
func sizeThatFits(proposal _: ProposedViewSize, subviews _: Subviews, cache _: inout ()) -> CGSize {
print("\(name) layout")
return .init(width: 100, height: 100)
}
func placeSubviews(in _: CGRect, proposal _: ProposedViewSize, subviews _: Subviews, cache _: inout ()) {}
}
上面的代码创立了一个固定回来 100 * 100 需求尺度的布局容器,在父视图询问其需求尺度时将经过控制台报告给咱们。
判别视图正准备烘托
虽然 SwiftUI 视图并没有供给能够展示该进程的 API,不过咱们能够运用 UIViewControllerRepresentable 协议来包装一个 UIViewController ,并经过它的生命周期回调办法来确定当时的状态。
struct ViewHelper: UIViewControllerRepresentable {
func makeUIViewController(context _: Context) -> HelperController {
return HelperController()
}
func updateUIViewController(_: HelperController, context _: Context) {
}
// SwiftUI 4 新增办法
func sizeThatFits(_: ProposedViewSize, uiViewController _: HelperController, context _: Context) -> CGSize? {
print("helper layout")
return .init(width: 50, height: 50)
}
}
final class HelperController: UIViewController {
override func viewWillAppear(_: Bool) {
print("will appear(render)")
}
}
在上面的代码中,sizeThatFits 与 Layout 协议的 sizeThatFits 调用机遇共同,都是在布局进程中,父视图向子视图询问需求尺度时拜访。viewWillAppear 则是在 UIViewController 被呈现前( 能够理解为烘托前 ),会由 UIKit 调用。
经过 UIViewControllerRepresentable 封装的“视图”并非真实的视图,对于 SwiftUI 来说,它就是一块给出了需求尺度的黑洞,因而并不存在求值一说。
整合
有了上面的东西,经过下面的代码,咱们便能够完好地了解一个 SwiftUI 视图的处理进程,以及 onAppear 的调用机遇。
struct LayoutTest: View {
var body: some View {
MyLayout(name: "outer") {
let _ = print("outer evaluate")
MyLayout(name: "inner") {
let _ = print("inner evaluate")
ViewHelper()
.onAppear {
print("helper onAppear")
}
}
.onAppear {
print("inner onAppear")
}
}
.onAppear {
print("outer onAppear")
}
}
}
输出如下:
outer evaluate
inner evaluate
outer layout
inner layout
helper layout
outer onAppear
helper onAppear
inner onAppear
will appear(render)
分析
经过上面的输出,能够清楚地了解视图处理的全进程:
- SwiftUI 首先对视图进行求值( 由外向内 )
- 在悉数求值完毕后开始进行布局( 由父视图到子视图 )
- 在布局完毕后,调用视图对应的 onAppear 闭包( 次序不明,不要假定 onAppear 之间的执行次序 )
- 烘托视图
由此能够证明,onAppear 确实是在布局之后,烘托之前被调用的。
解答
回到本文开始的问题。
榜首段代码
- 对 VStack 进行求值
- 核算到 Text ,创立 Text 实例
- 创立实例时,需求调用 getWord 来获取参数
- 此时因为 newWords 数组为空,因而呈现数组越界的过错
也就是说,在榜首段代码报错时,该视图甚至还没有进入到布局阶段,就更不必提调用 onAppear 了。
在不考虑运用绝对索引值是否正确的状况下,经过下面的代码,便能够避免问题的呈现:
if !newWords.isEmpty {
Text(getWord(at:0))
}
第二段代码
- 对 List 进行求值
- 因为 ForEach 会根据 newWords 的数量进行子视图的处理,因而虽然此时 newWords 为空,但也不会有问题
- 完成布局
- 调用 onAppear 闭包,给 newWords 赋值
- 因为 newWords 是该视图的 Source of truth ,发生改变后,导致视图从头改写
- 重复上面的进程,此时 newWords 已经有值了,ForEach 将正常处理所有的子视图
总结
在本文中,咱们经过 SwiftUI 4 供给的新东西清晰了 onAppear 的调用机遇,或许这是新 API 开发时未曾想到的功能使用。
希望本文能够对你有所帮助。一起也欢迎你经过 Twitter、 Discord 频道 或博客的留言板与我进行沟通。
订阅下方的 邮件列表,能够及时取得每周的 Tips 汇总。
原文发表在我的博客wwww.fatbobman.com
欢迎订阅我的大众号:【肘子的Swift记事本】