SwiftUI - 如何使用 ObservedObject 或 EnvironmentObject 存储 GeometryReader 数据?

Posted

技术标签:

【中文标题】SwiftUI - 如何使用 ObservedObject 或 EnvironmentObject 存储 GeometryReader 数据?【英文标题】:SwiftUI - How can I use ObservedObject or EnvironmentObject to store GeometryReader data? 【发布时间】:2021-09-17 04:13:36 【问题描述】:

我正在尝试遵循为应用程序创建的设计,该应用程序在屏幕中间放置了一些对象。

对象的尺寸和内边距应与设备的屏幕尺寸成正比,这意味着如果屏幕大于我们在设计中作为基础的屏幕(在这种情况下,基础是 iPhone 11 屏幕),它们应该看起来更大)。另外,这些物体里面的物体比较多,也应该和屏幕大小成正比。例如:放置在 RoundedRectangle 边框内的 Text 视图,如果屏幕大于用作基础的屏幕,则字体应该增长;或另一个图像中的图像。在这些示例中,对象及其内部的对象都应与屏幕大小成正比。

到目前为止,我们正在使用 GeometryReader 来完成这项工作。我们这样做的方式需要我们在为屏幕及其视图定义的每个文件中使用 GeometryReader。一旦我们有了 GeometryReader 数据,我们就可以使用 Scale 结构来获取对象的正确比例。

这里是示例代码:

GeometryReaderSampleView.swift

import SwiftUI

struct GeometryReaderSampleView: View 
    var body: some View 
        NavigationView 
            GeometryReader  metrics in
                ZStack 
                    VStack 
                        LoginMainDecorationView(Scale(geometry: metrics))
                        Spacer()
                    
                    
                    VStack 
                        HStack 
                            GreenSquareView(Scale(geometry: metrics))
                            Spacer()
                        
                        .offset(x: 29, y: Scale(geometry: metrics).vertical(300.0))
                        Spacer()
                    
                
            
        
    


struct GreenSquareView: View 
    let scale:Scale
    
    init (_ scale:Scale) 
        self.scale = scale
    
    
    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))
        
    


struct LoginMainDecorationView: View 
    let scale:Scale
    
    init (_ scale:Scale) 
        self.scale = scale
    
    
    var body: some View 
            HStack 
                Image(systemName: "cloud.rain")
                    .resizable()
                    .frame(width: scale.horizontal(84), height: scale.horizontal(68), alignment: .leading)
                    .offset(x: 0, y: scale.vertical(200.0))
                Spacer()
                Image(systemName: "cloud.snow")
                    .resizable()
                    .frame(width: scale.horizontal(119), height: scale.horizontal(91), alignment: .trailing)
                    .offset(x: scale.horizontal(-20.0), y: scale.vertical(330.0))
            
    


struct GeometryReaderSampleView_Previews: PreviewProvider 
    static var previews: some View 
        GeometryReaderSampleView()
    

Scale.swift

import SwiftUI

struct Scale 
    // Size of iPhone 11 Pro
    let originalWidth:CGFloat = 375.0
    let originalHeight:CGFloat = 734.0
    
    let horizontalProportion:CGFloat
    let verticalProportion:CGFloat
    
    init(screenWidth:CGFloat, screenHeight:CGFloat) 
        horizontalProportion =  screenWidth / originalWidth
        verticalProportion = screenHeight / originalHeight
    
    
    init(geometry: GeometryProxy) 
        self.init(screenWidth: geometry.size.width, screenHeight: geometry.size.height)
    
    
    func horizontal(_ value:CGFloat) -> CGFloat 
        return value * horizontalProportion
    
    
    func vertical(_ value:CGFloat) -> CGFloat 
        return value * verticalProportion
    

问题/请求

我想简化此代码并将 GeometryReader 数据(带有其信息的 Scale 结构)存储在 ObservedObject 或 EnvironmentObject 中,以便我们可以在整个项目的不同视图和文件中使用它。这样做的问题是,在加载视图之前我们无法获取 GeometryReader 数据,一旦加载了视图,我相信我们不能再声明 ObservedObject 或 EnvironmentObject(对吗?)。

我知道有一种方法可以在不使用 GeometryReader 的情况下获取屏幕尺寸,如下所示:How to get the iPhone's screen width in SwiftUI?。但是,如果我使用 GeometryReader 来获取另一个视图中的视图的大小,我希望也将其信息存储起来。

我们的目标是不在每个需要使用缩放的视图中使用此代码:

let scale:Scale
        
init (_ scale:Scale) 
    self.scale = scale

而是使用 ObservedObject 或 EnvironmentObject 从需要它的视图中获取比例数据。那么,如何使用 ObservedObject 或 EnvironmentObject 来存储 GeometryReader 数据呢?

【问题讨论】:

【参考方案1】:

我倾向于认为您这样做是在与 SwiftUI 的一般原则作斗争(即基于屏幕尺寸而不是使用与屏幕尺寸无关的内置 SwiftUI 布局原则,如 padding)。不过,假设您想继续执行该计划,我建议您使用 @Envrionment 值。我不认为它必须是 @EnvironmentObject,因为 Scalestruct,并且没有令人信服的理由让引用类型来装箱值。

这是一个简单的例子:

private struct ScaleKey: EnvironmentKey 
  static let defaultValue = Scale(screenWidth: -1, screenHeight: -1)


extension EnvironmentValues 
  var scale: Scale 
    get  self[ScaleKey.self] 
    set  self[ScaleKey.self] = newValue 
  


struct ContentView: View 
    
    var body: some View 
        GeometryReader  metrics in
            SubView()
                .environment(\.scale, Scale(geometry: metrics))
        
    


struct SubView : View 
    @Environment(\.scale) private var scale : Scale
    
    var body: some View 
        Text("Scale: \(scale.horizontal(1)) x \(scale.vertical(1))")
    

【讨论】:

感谢您的回复!这个解决方案对我有用。关于 SwiftUI 的原则以及它是如何打败它们的,如果给定基本屏幕尺寸,你将如何在 SwiftUI 中做一个设计,其中包含一个矩形框(给定大小)和文本(给定字体大小,让我们说 20) 在里面。如果我们有一个屏幕尺寸比基础屏幕更大的设备,我们如何才能按比例增加内部对象的尺寸?如果不增加这些对象,屏幕的设计将不会适用于所有屏幕尺寸。 关于盒子,我会使用padding 来调整它相对于周围环境的大小。对于文本大小,您可能是对的,您必须使用比例尺。但是,我个人会尽量避免这种情况,因为它会破坏用户对字体大小的可访问性设置。 您可以使用绿色复选标记接受答案。如果你觉得它有帮助,你也可以考虑使用箭头来投票。 谢谢!公认!我认为对于文本,我们最终将使用缩放。不太理想,但平面设计师给我们的设计就是这样。

以上是关于SwiftUI - 如何使用 ObservedObject 或 EnvironmentObject 存储 GeometryReader 数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SwiftUI 在 UICollectionViewCell 中填充内容

如何使用 SwiftUI 呈现警报

如何使用 SwiftUI 在首次应用启动时启动教程?

如何使用 SwiftUI 获取当前位置?

如何使用 SwiftUI 将数组绑定到列表

SwiftUI:如何使用 UserDefaults 保存选择器数据?