SwiftUI中@StateObject多次初始化问题排查与解决咨询
你遇到的@StateObject随sheet每次打开都重新初始化的问题,核心在于SwiftUI视图层级与@StateObject的生命周期绑定逻辑:
- @StateObject的生命周期并非和视图结构体实例绑定,而是和视图在当前活跃的视图层级中的存在周期绑定。
- sheet属于独立的展示层级(presentation hierarchy),当sheet关闭时,整个展示层级会被销毁,其中的@StateObject也会随之释放。
- 虽然你预先创建了ChildView实例并复用,但每次打开sheet时,这个实例会被重新附加到新创建的展示层级中。SwiftUI会将其视为视图在新层级的"首次出现",因此会重新初始化@StateObject。
至于@ObservedObject能正常工作,是因为它的所有权不在SwiftUI,而是由ChildView结构体实例持有——只要ChildView实例存在,ViewModel就不会被销毁。但这确实违反了SwiftUI的最佳实践:@ObservedObject应该由父视图传入,而非视图自身创建。
方案一:父视图持有ViewModel(推荐,符合单向数据流)
将ViewModel的所有权上移到ContentView,用@StateObject持有后传递给ChildView。这种方式既保证ViewModel生命周期稳定,也符合SwiftUI的数据流设计。
修改后的ContentView:
struct ContentView: View { @State private var displayingChildView = false @StateObject private var childViewModel = ViewModel() var body: some View { Button(action: { displayingChildView.toggle() }, label: { Text("Display child view") }) .sheet(isPresented: $displayingChildView) { ChildView(viewModel: childViewModel) } } }
修改后的ChildView:
struct ChildView: View { @ObservedObject var viewModel: ViewModel init(viewModel: ViewModel) { self.viewModel = viewModel print("init() of ChildView") } var body: some View { VStack { Button(action: { viewModel.add() }, label: { Text("Add 1 to count") }) Text("Count: \(viewModel.count)") } } }
(若使用SwiftUI 5+,可将@ObservedObject替换为@Bindable,语法更简洁)
方案二:使用.sheet(item:)控制sheet展示
如果希望ViewModel的生命周期和sheet绑定(即每次打开sheet创建新ViewModel,关闭时销毁),但不想每次打开都重复初始化,可以用.sheet(item:)替代.sheet(isPresented:)。这种方式通过可标识对象关联sheet状态,SwiftUI会自动管理视图层级的状态。
首先定义标识结构体:
struct SheetTrigger: Identifiable { let id = UUID() }
修改后的ContentView:
struct ContentView: View { @State private var sheetTrigger: SheetTrigger? var body: some View { Button(action: { sheetTrigger = SheetTrigger() }, label: { Text("Display child view") }) .sheet(item: $sheetTrigger) { _ in ChildView() } } }
ChildView保持原代码不变:
struct ChildView: View { @StateObject private var viewModel = ViewModel() init() { print("init() of ChildView") } var body: some View { VStack { Button(action: { viewModel.add() }, label: { Text("Add 1 to count") }) Text("Count: \(viewModel.count)") } } }
这种方案下,每次打开sheet会创建新的ChildView和ViewModel,但关闭sheet时才会销毁,不会出现每次打开都重复初始化@StateObject的问题。
内容的提问来源于stack exchange,提问作者Jonathan

