1
votes

I want both the buttons to have equal height similar to Equal Height constraint in UIKit.

  • Don't want to specify the frame, let SwiftUI handle it but the elements in HStack should be of the same height.
  • Buttons should have equal width and height and adapt to longer text and increases their frame size
  • Both the buttons should display their complete text (Font to scale / Fit shouldn't be used)

Sample Code


struct SampleView: View {
    var body: some View {
        
        GeometryReader { gr in
            VStack {
                ScrollView {
                    VStack {
                        // Fills whatever space is left
                        Rectangle()
                            .foregroundColor(.clear)

                        Image(systemName: "applelogo")
                            .resizable()
                            .frame(width: gr.size.width * 0.5, height: gr.size.height * 0.3, alignment: .center)
                            //.border(Color.blue)
                            .padding(.bottom, gr.size.height * 0.06)


                        Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
                            .fontWeight(.regular)
                            .foregroundColor(.green)
                            .multilineTextAlignment(.center)
                            .padding(.horizontal, 40)
                            .layoutPriority(1)


                        // Fills 15 %
                        Rectangle()
                            .frame(height: gr.size.height * 0.12)
                            .foregroundColor(.clear)

                    DynamicallyScalingView()
                        .padding(.horizontal, 20)
                        .padding(.bottom, 20)

                    }

                    // Makes the content stretch to fill the whole scroll view, but won't be limited (it can grow beyond if needed)
                    .frame(minHeight: gr.size.height)
                }
            }
        }
    }
}

struct DynamicallyScalingView: View {
    @State private var labelHeight = CGFloat.zero     // << here !!

    var body: some View {
        HStack {
            Button(action: {
            }, label: {
                Text("Button 1")
            })
            .foregroundColor(Color.white)
            .padding(.vertical)
            .frame(minWidth: 0, maxWidth: .infinity)
            .frame(minHeight: labelHeight)
            .background(Color.blue)
            .cornerRadius(8)

            Button(action: {
            }, label: {
                Text("Larger Button 2 Text Text2")
            })
            .foregroundColor(Color.white)
            .padding(.vertical)
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.blue)
            .cornerRadius(8)
            .background(GeometryReader {      // << set right side height
                Color.clear.preference(key: ViewHeightKey.self,
                                       value: $0.frame(in: .local).size.height)
            })
        }
        .onPreferenceChange(ViewHeightKey.self) { // << read right side height
            self.labelHeight = $0        // << here !!
        }
        .padding(.horizontal)
    }
}

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value + nextValue()
    }
}

struct SampleView_Previews: PreviewProvider {
    static var previews: some View {
        SampleView().previewDevice("iPhone SE (2nd generation)")
    }
}

1
In SwiftUI, parents view cant "force" child view a size like in UIKit.Yodagama
Does this answer your question stackoverflow.com/a/62451599/12299030?Asperi
What should happen when there is a long text on the 2nd button, should it get clipped?user1046037
@Asperi, Thank you for pointing me in the right direction. Yes, it does work with the way you have pointed out using the preference key. Unfortunately, my view has more elements, let me update the question.Pablo Dev

1 Answers

2
votes

You can set the max value in the ViewHeightKey preference key:

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = max(value, nextValue()) // set the `max` value (from both buttons)
    }
}

and then read view height from both buttons and force vertical fixedSize:

struct DynamicallyScalingView: View {
    @State private var labelHeight = CGFloat.zero

    var body: some View {
        HStack {
            Button(action: {}, label: {
                Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
            })
                .foregroundColor(Color.white)
                .padding(.vertical)
                .frame(minWidth: 0, maxWidth: .infinity)
                .frame(minHeight: labelHeight) // min height for both buttons
                .background(Color.blue)
                .cornerRadius(8)
                .fixedSize(horizontal: false, vertical: true) // expand vertically
                .background(GeometryReader { // apply to both buttons
                    Color.clear
                        .preference(
                            key: ViewHeightKey.self,
                            value: $0.frame(in: .local).size.height
                        )
                })

            Button(action: {}, label: {
                Text("jahlsd")
            })
                .foregroundColor(Color.white)
                .padding(.vertical)
                .frame(minWidth: 0, maxWidth: .infinity)
                .frame(minHeight: labelHeight)
                .background(Color.blue)
                .cornerRadius(8)
                .fixedSize(horizontal: false, vertical: true)
                .background(GeometryReader {
                    Color.clear
                        .preference(
                            key: ViewHeightKey.self,
                            value: $0.frame(in: .local).size.height
                        )
                })
        }
        .onPreferenceChange(ViewHeightKey.self) {
            self.labelHeight = $0
        }
        .padding(.horizontal)
    }
}

Note: as the buttons are similar now, the next step would be to extract them as another component to avoid duplication.