skip to Main Content

I’m trying to get continuity in my views across all device sizes but my frames are a fixed size. I need to get them to change depending on the size of the device. I have tried using Geometry Reader but it didn’t seem to work in some cases. Is Geometry Reader the only way or are there better alternatives?

This is an example of where Geometry Reader made the frame respond weirdly.

(This view is supposed to be a rectangle ’tile’)

struct ProductCardView: View {
         
    var body: some View {
        
          GeometryReader { geometry in
        
        
        VStack {
       
            Image("ProductImage")
   
            Text("Product Name)

        }
                    
        //MARK: Framing Before Geometry Reader .frame(width: 160, height: 210)       
        .frame(width: geometry.size.width * 0.40)
        .frame(height: geometry.size.height * 0.27)
      
       } 
    }
}


How do I make frames dynamic?

Here is how it is turning out:

enter image description here

2

Answers


  1. Uses Color.clear to fill all screen then calculate its size using GeometryReader and PreferenceKey.

    struct ContentView: View {
        @State private var screenSize: CGSize = .zero
        
        var body: some View {
            ZStack {
                GeometryReader { geometryProxy in
                    Color.clear.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
                }
                .onPreferenceChange(SizePreferenceKey.self) { size in
                    self.screenSize = size
                }
                VStack {
                    Image("ProductImage")
                    Text("Product Name")
                }
                .frame(width: self.screenSize.width * 0.40,
                       height: self.screenSize.height * 0.27)
            }
        }
    }
    
    fileprivate struct SizePreferenceKey: PreferenceKey {
        static var defaultValue: CGSize = .zero
        static func reduce(value: inout CGSize, nextValue: () -> CGSize) { }
    }
    

    For ScrollView

    • Just move the calculate size to the view contains ScrollView
    struct ContentView: View {
        @State var screenSize: CGSize = .zero
        
        var body: some View {
            ZStack {
                GeometryReader { geometryProxy in
                    Color.clear.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
                }
                .onPreferenceChange(SizePreferenceKey.self) { size in
                    self.screenSize = size
                }
                ScrollView { ForEach(0 ..< 4) { _ in
                    CardView(screenSize: $screenSize)
                }}
            }
        }
    }
    
    struct CardView: View {
        @Binding var screenSize: CGSize
        
        var body: some View {
            VStack {
                Circle()
                Text("ProducImage")
            }
            .frame(width: screenSize.width * 0.40,
                   height: screenSize.height * 0.27)
        }
    }
    
    Login or Signup to reply.
  2. Add the following extension in some file

    extension UIScreen{
       static let screenWidth = UIScreen.main.bounds.size.width
       static let screenHeight = UIScreen.main.bounds.size.height
       static let screenSize = UIScreen.main.bounds.size
    }
    

    And whenever you want to reference a value that should be a size relative to screen width or screen height, you can call UIScreen.screenWidth for example. For example, your frame modifier

    .frame(width: screenSize.width * 0.40, height: screenSize.height * 0.27)
    

    can now be

    .frame(width: UIScreen.screenWidth * 0.40, height: UIScreen.screenHeight * 0.27)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search