2

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

4
  • In SwiftUI, parents view cant "force" child view a size like in UIKit. Commented Jan 7, 2021 at 5:46
  • Does this answer your question stackoverflow.com/a/62451599/12299030? Commented Jan 7, 2021 at 6:41
  • What should happen when there is a long text on the 2nd button, should it get clipped? Commented Jan 7, 2021 at 6:53
  • @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. Commented Jan 7, 2021 at 15:46

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

10 Comments

Buttons should have equal width and height. Using fixedSize() make buttons frame clip to intrinsic size.
@PabloDev You said: "Button adapts to longer text and increases its frame size" and "Both the buttons should display their complete text". If the text is longer than half of screen width one of these constraints is invalid.
Maybe I was not clear, updated the post description. Thank you for pointing it out.
@PabloDev Thanks but I still don't fully get it. What if one button has a text of 2/3 of screen width? How the layout should look like then? And how the layout should be different from what you have now (both buttons are equal currently)?
Buttons will increase in height to accommodate the text. Currently, it behaves so but the frame (height) of both the buttons are not equal. Please do let me know if it's not clear. Ex: we can use Environment overrides in Xcode to increase the adaptability font size, both buttons should have equal width and height irrespective of text content. As stated in the earlier requirement, font to scale / fixed Size/font to fill should not be used. Font size should adapt to the dynamic font size chosen on the device
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.