SwiftUI使用EnvironmentObject时出现运行时问题的原因咨询
核心原因:@ScaledMetric的运行依赖View上下文
@ScaledMetric是SwiftUI专为View设计的属性包装器,它的核心功能(响应系统动态字体大小、屏幕缩放变化)完全依赖当前View所在的Environment环境。只有当它被定义在View结构体内部时,SwiftUI才会自动将该View的环境上下文绑定给它,让它能实时感知系统设置变化并更新值。
你的代码里,@ScaledMetric被放在了普通结构体Size中,而Size又被ObservableObject类Style持有。ObservableObject是独立于View生命周期的对象,它不属于任何View的上下文环境,所以当你在TestView的body中访问style.size.inputPadding时,@ScaledMetric找不到它需要的View环境支持,就会抛出警告,并且此时它只能返回默认值,完全无法响应系统设置的变化。
为什么会被判定为「在View外部访问」?
虽然你是在TestView的body里写的代码,但实际读取的是ObservableObject实例中的Size结构体属性。Size不是View,它的@ScaledMetric属性没有被SwiftUI的View环境托管,相当于脱离了View的上下文去访问依赖环境的属性,因此系统判定为「在View外部访问」。
旧版本无异常的原因
Xcode 14.1之前的SwiftUI版本没有严格校验@ScaledMetric的使用上下文,所以没有抛出这个警告,但底层逻辑一直如此——当时inputPadding同样不会响应系统动态字体/缩放的变化,只是问题没有被显性暴露出来。
修复方案参考
方案1:将@ScaledMetric移至View内部
把缩放相关的属性直接放在View里,让它处于正确的上下文环境中:
struct TestView: View { @EnvironmentObject private var style: Style @ScaledMetric(relativeTo: .body) private var inputPadding: CGFloat = 8 public var body: some View { Text("Hello World") .padding(inputPadding) } }
方案2:让Style绑定View环境动态更新
如果需要在全局Style中统一管理缩放值,可以在View中监听环境变化并同步给Style:
class Style: ObservableObject { @Published var inputPadding: CGFloat = 8 } struct TestView: View { @EnvironmentObject private var style: Style @Environment(\.dynamicTypeSize) private var dynamicTypeSize public var body: some View { Text("Hello World") .padding(style.inputPadding) .onChange(of: dynamicTypeSize) { newSize in // 根据动态类型大小计算对应的padding值 style.inputPadding = 8 * newSize.scalingFactor } } }
内容的提问来源于stack exchange,提问作者Mojtaba Hosseini

