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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 07:36:34