swiftswiftuigeometryreader

Understanding frame idealHeight, maxHeight in GeometryReader


I have 3 buttons displayed in a GeometryReader. I am having trouble understanding how to correctly use .frame(), so that the buttons ideally have a set height (e.g. 60) when they are displayed on devices (iPhone 14) where the GeometryReader height is sufficient for all 3 buttons to fit; otherwise set the height as GeometryReader height*0.3.

My code:

GeometryReader { geometry in
    VStack(alignment: .center) {
        ForEach((0..<3), id: \.self) {i in
            
            Button(action: {
                self.selected = i
            }) {
                VStack {
                    HStack {
                        Text(icons[i])
                            .font(.system(size: 24))
                        Text(buttonNames[i])
                            .font(.system(size: 20))
                    }
                }
                .foregroundColor(.black)
                .frame(maxWidth: UIScreen.main.bounds.width - 88, idealHeight: 60, maxHeight: (geometry.size.height * 0.3), alignment: .leading)
                .padding([.leading, .trailing])
                .background(
                    RoundedRectangle(cornerRadius: 20)
                        .fill(Color.white)
                )
                .overlay( self.selected == i ?
                          RoundedRectangle(cornerRadius: 20)
                    .stroke(LinearGradient(colors: [blue1, blue3], startPoint: .top, endPoint: .bottom), lineWidth: 2)
                          : nil
                )
            }
        }
    }.frame(width: geometry.size.width,
            height: geometry.size.height,
            alignment: .center)
}

Currently, it uses the maxHeight regardless of device. Why is this?


Solution

  • As I understand your goal you don't need a GeometryReader as SwiftUI is taking over a lot of work for you.

    You only have to define a maxHeight of 60 for the buttons.

    This means that the button frame will try to take a maximum height of 60 pixels, IF they are available. If not, the remaining space will be evenly divided by all views – in your case the three buttons.

    I set up a dummy element underneath the buttons, which you can enlarge to "squeeze" the buttons up and see what happens:

    enter image description here

    struct ContentView: View {
        
        @State private var selected = 0
        @State private var height = 300.0
    
        var body: some View {
            
            //        GeometryReader { geometry in
            VStack(alignment: .center) {
                ForEach((0..<3), id: \.self) {i in
                    
                    Button(action: {
                        self.selected = i
                    }) {
                        HStack {
                            Text("👉🏻")
                                .font(.system(size: 24))
                            Text("Button \(i+1)")
                                .font(.system(size: 20))
                        }
                        .foregroundColor(.black)
                        .frame(maxWidth: .infinity, maxHeight: 60, alignment: .leading)
                        .padding(.horizontal)
                        .background(
                            RoundedRectangle(cornerRadius: 20)
                                .fill(Color.white)
                        )
                        .overlay( self.selected == i ?
                                  RoundedRectangle(cornerRadius: 20)
                            .stroke(LinearGradient(colors: [.blue, .teal], startPoint: .top, endPoint: .bottom), lineWidth: 2)
                                  : nil
                        )
                    }
                }
                
                // other dummy element
                Color.gray
                    .frame(height: height)
                Slider(value: $height, in: 10...1000)
                
            }
            .padding()
        }
    }