1
votes

I am trying to follow the design created for an app which has some objects placed in the middle of the screen.

The objects should have a size and padding proportional to the device's screen size, meaning they should appear bigger if the screen is bigger than the screen we take as a base in the design (the base is an iPhone 11 screen in this case). In addition, these objects have more objects inside, which should also be proportional to the screen size. For example: a Text view placed whithin the borders of a RoundedRectangle for which the font should grow if the screen is bigger than the screen used as a base; or an image inside another image. In these examples, the object and the objects inside of it should all be proportional to the screen size.

So far, we are using GeometryReader to accomplish this. The way we are doing it needs us to use GeometryReader in each file we have defined for a screen and its views. Once we have GeometryReader data, we use the Scale struct to get the correct proportions for the objects.

Here is the sample code:

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
    }
}

The question / request

I would like to simplify this code and store the GeometryReader data (the Scale struct with its info) in an ObservedObject or an EnvironmentObject so that we can use it in different views and files all over the project. The problem with this is that we cannot get GeometryReader data until the view is loaded, and once the view is loaded I believe we cannot declare ObservedObject or EnvironmentObject anymore (is that correct?).

I know there could be a way to get the screen size without using GeometryReader as shown here: How to get the iPhone's screen width in SwiftUI?. But if I used GeometryReader to get the size of a view that is inside another view, I would like to have its information stored as well.

The goal would be not to use this code inside each view that needs to use scale:

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

and instead use ObservedObject or EnvironmentObject to get the scale data from the views that need it. Therefore, how can I use ObservedObject or EnvironmentObject to store GeometryReader data?