SwiftUI视图body重绘时机、生命周期及Demo异常问题咨询
SwiftUI视图body重计算时机与生命周期解析
一、body重计算触发时机
- 视图首次被添加到视图树时,会执行body计算
- 视图依赖的响应式状态(@State、@Binding、@ObservedObject、@EnvironmentObject等)发生变化时,SwiftUI会标记视图为待更新,在下一次渲染周期重新计算body
- 父视图body重计算时,若当前视图的身份标识(默认由类型+属性哈希值决定)发生变化,会触发该视图的body重计算
二、SwiftUI视图生命周期
SwiftUI采用状态驱动的生命周期,无UIKit式的明确生命周期方法,核心阶段如下:
- 实例化:视图结构体被创建(init调用),此时仅为值类型实例,未接入视图树
- 挂载视图树:首次计算body,生成对应底层平台(UIKit/AppKit)视图,完成首次渲染
- 更新:依赖状态变更 → 视图被标记为待更新 → 重新计算body → SwiftUI对比新旧视图差异,仅更新变化的底层视图部分
- 卸载视图树:视图不再被父视图引用时,结构体实例被销毁,对应底层视图也被释放
代码示例
struct ColorView:View { @State var changed = false init(){ print("ColorView init") } var body: some View { print("calculate ColorView's body") return ZStack{ Rectangle() .foregroundStyle(changed ? Color.red : Color.blue) Button(changed.description) { changed.toggle() print("ColorView's color changed") } .foregroundStyle(Color.black) } } } struct ListView:View { init(){ print("ListView init") } var body: some View { print("calculate ListView's body") return ZStack{ ScrollView { VStack{ ForEach(1..<30) { index in Text("\(index)") .frame(width: screenWidth, height: 50) } } } } } } enum Tabs{ case list,color } struct ContentView: View { @State var tab = Tabs.list var body: some View { ZStack { switch tab { case .list: ListView() case .color: ColorView() } VStack{ Spacer() Button("Change") { print("change page") tab = tab == .list ? .color : .list } .padding() .background { Color.white } } } } }
疑问解析
1. 切换页面时,SwiftUI会创建新的视图实例,但视图的body并非每次都会重新计算
切换tab时,ContentView的@State变量tab变更,触发父视图body重计算。由于视图结构体是值类型,每次都会创建新的ListView/ColorView实例,但body是否重计算取决于:
- 若视图是首次被挂载到视图树(比如首次切换到ColorView),必须计算body完成渲染
- 若视图是重新挂载,但无依赖状态变化、身份标识未变更,SwiftUI会复用之前的底层视图,跳过body重计算(比如切换回ListView的初始阶段)
2. ListView与ColorView为无共享状态的不同视图,默认显示ListView。每次切换标签页时,ColorView的body会被重新计算,但ListView不会;当修改ColorView中的@State属性changed后,每次切换标签页时ListView的body都会被重新计算,而ColorView不会
- 初始状态(changed未修改):
每次切换到ColorView时,新实例无关联的历史状态,属于首次挂载,因此触发body计算;而切换回ListView时,ListView无任何依赖状态变更,身份标识未变,SwiftUI复用底层视图,跳过body计算。 - 修改changed后:
ColorView的@State状态由SwiftUI框架单独存储(不绑定视图实例),切换回ColorView时,新实例会关联历史状态,此时changed值未发生变化,因此无需计算body;而ListView切换回来时,由于之前ColorView的状态变更导致父视图的渲染上下文更新范围扩大,且ListView未设置明确的身份标识(如.id()),SwiftUI无法确认是否可复用旧视图,因此触发body重计算。
3. 无论ColorView是新实例还是经历body重绘,总能根据changed属性显示正确颜色,但ListView的ScrollView滚动位置会在body重绘时重置
- ColorView状态保留:@State属性的存储独立于视图结构体实例,由SwiftUI在视图树中单独管理。即使创建新的ColorView实例,框架会自动将历史状态关联到新实例,因此能显示正确颜色。
- ListView滚动位置重置:ScrollView的滚动位置默认由底层视图维护,当ListView的body重计算时,会重新生成ScrollView实例,底层视图被重建,滚动位置随之重置。若要保留滚动位置,可通过
ScrollViewReader绑定滚动偏移量到@State或@AppStorage等状态容器中。
终端运行结果
-change page -ColorView init -calculate ColorView's body -change page -ListView init -change page -ColorView init -calculate ColorView's body -change page -ListView init -change page -ColorView init -calculate ColorView's body -ColorView's color changed #tap the button in ColorView -calculate ColorView's body -change page -ListView init -calculate ListView's body #the offset of ScrollView is reset -change page -ColorView init -change page -ListView init -calculate ListView's body #ColorView always have the correct color of all the case
内容的提问来源于stack exchange,提问作者Jerry
相关产品推荐
相关产品推荐

