Evel Knievel
Evel Knievel

Reputation: 255

SwiftUI: Two buttons with the same width/height

I have 2 buttons in an H/VStack. Both of them contain some text, in my example "Play" and "Pause". I would like to have that both buttons have the same width (and height) determined by the largest button. I have found some answers right here at SO but I can't get this code working unfortunately.

The following code illustrates the question:

import SwiftUI

struct ButtonsView: View {
    var body: some View {
        VStack {
            Button(action: { print("PLAY tapped") }){
                Text("Play")
            }

            Button(action: { print("PAUSE tapped") }) {
                Text("Pause")
            }
        }
    }
}

struct ButtonsView_Previews: PreviewProvider {
    static var previews: some View {
        ButtonsView()
    }
}

The tvOS preview from Xcode shows the problem:

enter image description here

I would be thankful for an explanation for newbies 🙂

Upvotes: 5

Views: 6676

Answers (5)

Robert Monfera
Robert Monfera

Reputation: 2137

You can implement the second custom layout example in the WWDC 2022 talk https://developer.apple.com/videos/play/wwdc2022/10056/ titled "Compose custom layouts with SwiftUI" which, if I understand the question, specifically solves it, for an arbitrary number of buttons/subviews. The example starts at the 7:50 mark.

Upvotes: 3

tintin
tintin

Reputation: 385

after reading hit and trial implementing SO solns etc finally resolved this issue posting so that newbies as well as intermediate can benefit

paste it and obtain equal size(square) views

VStack(alignment: .center){
           
            HStack(alignment:.center,spacing:0)
            {
               
                        Button(action: {}, label: {
                    
                                Text("Button one")
                        
                                .padding(35)
                                .foregroundColor(.white)
                                .font(.system(size: 12))
                                .background(Color.green)
                                .frame(maxWidth:.infinity,maxHeight: .infinity)
                                .multilineTextAlignment(.center)
                                .cornerRadius(6)
                        
                        
                        }).background(Color.green)
                            .cornerRadius(6)
                            .padding()
                            
         
                
               
                Button(action: {}, label: {
                    
                        Text("Button two")
                            .padding(35)
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                            .frame(maxWidth:.infinity,maxHeight: .infinity)
                            .background(Color.green)
                            .multilineTextAlignment(.center)
                            
                }) .background(Color.green)
                    .buttonBorderShape(.roundedRectangle(radius: 8))
                    .cornerRadius(6)
                    .padding()
                                    
            }.fixedSize(horizontal: false, vertical: true)
            

Add as many as buttons inside it. You can adjust it for VStack by adding only one button in hstack and add another button in another Hstack. I gave a general soln for both VStack and Hstack. You can also adjust padding of button as .padding(.leading,5) .padding(.top,5) .padding(.bottom,5) .padding(.trailing,5) to adjust the gaps between buttons

Upvotes: 1

Asperi
Asperi

Reputation: 257693

Here is run-time based approach without hard-coding. The idea is to detect max width of available buttons during drawing and apply it to other buttons on next update cycle (anyway it appears fluently and invisible for user).

Tested with Xcode 11.4 / tvOS 13.4

Required: Simulator or Device for testing, due to used run-time dispatched update

demo

struct ButtonsView: View {
    @State private var maxWidth: CGFloat = .zero
    var body: some View {
        VStack {
            Button(action: { print("PLAY tapped") }){
                Text("Play")
                    .background(rectReader($maxWidth))
                    .frame(minWidth: maxWidth)
            }.id(maxWidth) // !! to rebuild button (tvOS specific)

            Button(action: { print("PAUSE tapped") }) {
                Text("Pause Long Demo")
                    .background(rectReader($maxWidth))
                    .frame(minWidth: maxWidth)
            }.id(maxWidth) // !! to rebuild button (tvOS specific)
        }
    }

    // helper reader of view intrinsic width
    private func rectReader(_ binding: Binding<CGFloat>) -> some View {
        return GeometryReader { gp -> Color in
            DispatchQueue.main.async {
                binding.wrappedValue = max(binding.wrappedValue, gp.frame(in: .local).width)
            }
            return Color.clear
        }
    }

}

Upvotes: 6

davidev
davidev

Reputation: 8517

I think the best solution is to use GeometryReader, which resizes the width of the content of the Button. However, you need to check that you set a width of the Wrapper around the GeometryReader, because otherwise it would try to use the full screen width. (depends where you use that view, or if it is your primary view)

VStack
{
    GeometryReader { geo in

        VStack
        {
            Button(action: { print("PLAY tapped") }){
                Text("Play")
                .frame(width: geo.size.width)
            }
            .border(Color.blue)

            Button(action: { print("Pause tapped") }){
                Text("PAUSE")
                .frame(width: geo.size.width)
            }
            .border(Color.blue)
        }

    }
}
.frame(width: 100)
.border(Color.yellow)

... which will look like that.

Upvotes: -1

ncf031
ncf031

Reputation: 7

What happens if you put a Spacer() right after the Text("Play")? I think that might stretch out the 'Play' button.

Or maybe before and after Text("Play").

Upvotes: -3

Related Questions