SwiftUI:如何基于Environment初始化ObservableObject?
解决SwiftUI中基于Size Class初始化ObservableObject的问题
方案一:使用嵌套视图传递初始化参数
核心思路是先在父视图中获取horizontalSizeClass,再用该值初始化Settings对象,最后传递给内部视图。这样内部视图的初始状态从一开始就完全匹配尺寸类要求,不会出现先显后隐的动画问题。
修改后的代码:
import SwiftUI class Settings: ObservableObject { @Published var isVisible: Bool // 新增带初始值的初始化方法 init(initialVisible: Bool) { self.isVisible = initialVisible } } struct ContentView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass var body: some View { // 确保拿到尺寸类后再渲染内部视图 if let sizeClass = horizontalSizeClass { ContentViewInternal(settings: Settings(initialVisible: sizeClass == .regular)) } else { // 尺寸类未准备好时的占位视图 Color.clear } } } // 内部视图负责具体UI渲染 struct ContentViewInternal: View { @StateObject var settings: Settings @State var opacity: CGFloat // 根据Settings的初始值同步opacity的初始状态 init(settings: Settings) { _settings = StateObject(wrappedValue: settings) _opacity = State(initialValue: settings.isVisible ? 1 : 0) } var body: some View { VStack { Button("Toggle Visibility") { settings.isVisible.toggle() } .onReceive(settings.$isVisible) { newValue in // 仅在用户手动切换时触发动画 withAnimation(.linear(duration: 2.0)) { opacity = newValue ? 1 : 0 } } Rectangle() .frame(width: 100, height: 100) .foregroundColor(.blue) .opacity(opacity) } } }
优势:
- 初始状态完全匹配尺寸类要求,无动画闪烁
- 天然支持尺寸类变化(比如iPad分屏),只需在
ContentView中添加.onChange(of: horizontalSizeClass)即可更新状态
方案二:添加初始化标志避免初始动画
如果不想拆分视图,可以给Settings添加一个初始化标志,用来跳过初始设置时的动画触发:
修改后的代码:
import SwiftUI class Settings: ObservableObject { @Published var isVisible: Bool = true // 标记是否为初始设置阶段 var isInitialSetup: Bool = true } struct ContentView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @StateObject var settings = Settings() @State var opacity: CGFloat = 1 var body: some View { VStack { Button("Toggle Visibility") { settings.isVisible.toggle() } .onReceive(settings.$isVisible) { newValue in // 仅在非初始阶段触发动画 if !settings.isInitialSetup { withAnimation(.linear(duration: 2.0)) { opacity = newValue ? 1 : 0 } } } Rectangle() .frame(width: 100, height: 100) .foregroundColor(.blue) .opacity(opacity) } .onAppear { let shouldBeVisible = horizontalSizeClass == .regular // 同步更新状态和透明度,避免闪烁 settings.isVisible = shouldBeVisible opacity = shouldBeVisible ? 1 : 0 // 标记初始设置完成 settings.isInitialSetup = false } } }
优势:
- 无需拆分视图,代码结构更紧凑
- 仅需添加一个标志变量即可解决问题
方案三:iOS17+ 使用@Observable简化代码
如果你的项目基于iOS17+,可以使用新的@Observable宏替代ObservableObject,代码会更简洁:
import SwiftUI @Observable class Settings { var isVisible: Bool init(initialVisible: Bool) { self.isVisible = initialVisible } } struct ContentView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass var body: some View { if let sizeClass = horizontalSizeClass { ContentViewInternal(settings: Settings(initialVisible: sizeClass == .regular)) } else { Color.clear } } } struct ContentViewInternal: View { @State var settings: Settings @State var opacity: CGFloat init(settings: Settings) { _settings = State(wrappedValue: settings) _opacity = State(initialValue: settings.isVisible ? 1 : 0) } var body: some View { VStack { Button("Toggle Visibility") { settings.isVisible.toggle() } .onChange(of: settings.isVisible) { newValue in withAnimation(.linear(duration: 2.0)) { opacity = newValue ? 1 : 0 } } Rectangle() .frame(width: 100, height: 100) .foregroundColor(.blue) .opacity(opacity) } } }
优势:
- 无需使用
@Published和@StateObject,语法更简洁 - 苹果官方推荐的新数据绑定方案
内容的提问来源于stack exchange,提问作者viedev
相关产品推荐
相关产品推荐

