You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI视图自动旋转时重复初始化问题的解决方案咨询

解决SwiftUI中设备旋转时@State模型被重新初始化的问题

问题代码

struct TestContentView: View {
    @Environment(\.verticalSizeClass) var verticalSizeClass
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var body: some View {
        TestView()
            .ignoresSafeArea()
    }
}

struct TestView: View { 
    @State var model:TestModel = TestModel()
    
    var body: some View {
        Text("Count \(model.counter)")
    }
}

@Observable
class TestModel {
    var counter:Int = 0
    
    deinit {
        print("Calling deinit on TestModel")
    }
}

问题描述

尽管TestModel通过@State变量持有,但设备自动旋转时,TestView和TestModel仍会被重新初始化,不符合预期。已知问题根源是父视图TestContentView声明了size class环境变量,导致TestView被重置。将TestModel移到父视图初始化是一种解决方法,但对于较深的视图层级来说,扩展性不佳。请问针对该问题的正确解决方法是什么,尤其当视图层级较深且上层包含horizontalSizeClass或verticalSizeClass时?


解决方案

1. 用@StateObject替代@State管理Observable类

你的TestModel是@Observable标记的引用类型,应该使用@StateObject而非@State来持有。@State专为值类型设计,而@StateObject负责管理引用类型的生命周期,确保视图重建时对象不会被销毁重建。

修改TestView:

struct TestView: View { 
    @StateObject var model: TestModel = TestModel() // 替换@State为@StateObject
    
    var body: some View {
        Text("Count \(model.counter)")
    }
}

这样即使父视图因size class变化重建,TestView中的model实例会被保留,不会触发deinit。

2. 隔离size class读取逻辑,避免父视图重建传递到子视图

如果必须在父视图中使用size class,可以通过onChange监听变化,而非直接在结构体顶部声明环境变量(直接声明会触发父视图重建,进而导致子视图重置)。

修改TestContentView:

struct TestContentView: View {
    var body: some View {
        TestView()
            .ignoresSafeArea()
            .onChange(of: [
                EnvironmentValues.verticalSizeClass,
                EnvironmentValues.horizontalSizeClass
            ]) { newValue in
                // 在这里处理size class变化的业务逻辑
                print("Size class changed: \(newValue)")
            }
    }
}

如果需要根据size class调整布局,也可以用@Environment结合Group或ViewBuilder,让SwiftUI尝试复用子视图实例:

struct TestContentView: View {
    @Environment(\.verticalSizeClass) var verticalSizeClass
    
    var body: some View {
        Group {
            if verticalSizeClass == .compact {
                TestView()
            } else {
                TestView()
            }
        }
        .ignoresSafeArea()
    }
}

3. 通过环境(Environment)传递模型(适配深层视图层级)

如果视图层级较深,不想逐层传递模型,可以将TestModel注入到环境中,让深层视图直接从环境获取,同时用@StateObject保证模型生命周期不受上层视图重建影响。

首先扩展EnvironmentValues:

extension EnvironmentValues {
    var testModel: TestModel {
        get { self[TestModelKey.self] }
        set { self[TestModelKey.self] = newValue }
    }
}

private struct TestModelKey: EnvironmentKey {
    static let defaultValue: TestModel = TestModel()
}

然后在父视图中持有模型并注入环境:

struct TestContentView: View {
    @Environment(\.verticalSizeClass) var verticalSizeClass
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @StateObject private var testModel = TestModel()
    
    var body: some View {
        TestView()
            .ignoresSafeArea()
            .environment(\.testModel, testModel)
    }
}

最后在TestView中从环境获取模型:

struct TestView: View { 
    @Environment(\.testModel) var model
    
    var body: some View {
        Text("Count \(model.counter)")
    }
}

这种方式下,模型的生命周期由父视图的@StateObject管理,无论上层视图如何重建,模型都不会被销毁,深层视图也能直接访问。


关键原理

  • @State适用于值类型(如struct、enum),视图重建时SwiftUI会保留其值,但无法保证引用类型的实例不被重新初始化。
  • @StateObject专为Observable引用类型设计,SwiftUI会维护其生命周期,确保视图重建时实例被保留。
  • 父视图声明@Environment(\.sizeClass)后,size class变化会触发父视图重建,若子视图未用@StateObject或环境传递模型,就会被重新初始化。

内容的提问来源于stack exchange,提问作者Deepak Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:18:23