SwiftUI如何使用ObservedObject或EnvironmentObject存储GeometryReader缩放数据
问题判断说明
你提到的「视图加载完成后不能再声明ObservedObject或EnvironmentObject」的判断是错误的。你可以提前在App根节点声明ObservableObject对象,在GeometryReader拿到尺寸后更新对象内的属性,通过响应式更新同步到所有子视图。
实现方案
步骤1:定义可观测的Scale存储类
首先创建继承自ObservableObject的存储类,用来存储全局的Scale数据,支持响应式更新:
import SwiftUI class ScaleStore: ObservableObject { // 全局屏幕尺寸对应的Scale @Published var globalScale: Scale? = nil // 可按需新增字段存储不同嵌套场景的局部Scale数据 @Published var nestedCardScale: Scale? = nil }
原有Scale结构体无需修改,可以直接复用。
步骤2:在App入口注入ScaleStore
在App根节点初始化ScaleStore并通过环境变量注入到整棵视图树:
@main struct ScaleDemoApp: App { @StateObject private var scaleStore = ScaleStore() var body: some Scene { WindowGroup { GeometryReaderSampleView() .environmentObject(scaleStore) } } }
步骤3:在GeometryReader中更新Scale数据
修改根视图的GeometryReader逻辑,拿到尺寸后更新ScaleStore的对应属性,同时适配屏幕旋转、分屏等尺寸变化场景:
struct GeometryReaderSampleView: View { @EnvironmentObject var scaleStore: ScaleStore var body: some View { NavigationView { GeometryReader { metrics in ZStack { VStack { LoginMainDecorationView() Spacer() } VStack { HStack { GreenSquareView() Spacer() } .offset(x: 29, y: scaleStore.globalScale?.vertical(300.0) ?? 0) Spacer() } } .onAppear { scaleStore.globalScale = Scale(geometry: metrics) } .onChange(of: metrics.size) { newSize in scaleStore.globalScale = Scale(screenWidth: newSize.width, screenHeight: newSize.height) } } } } }
步骤4:子视图直接获取Scale,无需传参
子视图可以删除原来的Scale属性和init传参逻辑,直接通过环境变量获取Scale数据:
struct GreenSquareView: View { @EnvironmentObject var scaleStore: ScaleStore // 增加默认值兼容加载阶段、预览场景的空值问题 private var scale: Scale { scaleStore.globalScale ?? Scale(screenWidth: 375, screenHeight: 734) } var body: some View { ZStack(alignment: .topLeading) { RoundedRectangle(cornerRadius: scale.horizontal(30)) .fill(Color.green) .frame(width: scale.horizontal(157), height: scale.horizontal(146)) Text("Here goes\nsome text") .font(.custom("TimesNewRomanPS-ItalicMT", size: scale.horizontal(20))) .padding(.top, scale.horizontal(29)) .padding(.leading, scale.horizontal(19)) VStack { Spacer() HStack { Spacer() Image(systemName: "heart.circle") .resizable() .frame(width: scale.horizontal(20), height: scale.horizontal(20)) .offset(x: scale.horizontal(-20), y: scale.vertical(-17.0)) } }.frame(width: scale.horizontal(157), height: scale.horizontal(146)) } } }
嵌套子视图尺寸获取方案
如果需要获取嵌套在父视图内部的子视图尺寸,更推荐使用自定义环境键的方案,无需额外维护ObservableObject,性能更优:
// 定义自定义环境键 extension EnvironmentValues { var localScale: Scale { get { self[LocalScaleKey.self] } set { self[LocalScaleKey.self] = newValue } } } private struct LocalScaleKey: EnvironmentKey { static let defaultValue: Scale = Scale(screenWidth: 375, screenHeight: 734) } // 父视图内获取局部尺寸并注入 struct CardContainerView: View { var body: some View { GeometryReader { metrics in VStack { CardChildView1() CardChildView2() } // 局部注入该视图树专属的Scale .environment(\.localScale, Scale(geometry: metrics)) } .frame(width: 300, height: 200) } } // 子视图直接获取局部Scale struct CardChildView1: View { @Environment(\.localScale) var scale var body: some View { Text("局部缩放文本") .font(.system(size: scale.horizontal(16))) } }
内容的提问来源于stack exchange,提问作者Tomas
相关产品推荐
相关产品推荐

