iosswiftswiftui

SwiftUI ViewThatFits with two equally - sized buttons


I want to present two equal width buttons side-by-side (easy), but arrange them vertically if there isn't enough space.

Sounds easy, but unfortunately I am struggling to prevent ViewThatFits from allowing one of the buttons to occupy two lines:

struct ContentView: View {
    var body: some View {
        ViewThatFits {
            HStack {
                Button {
                    
                } label: {
                    Text("OK")
                        .frame(maxWidth: .infinity)
                }
                Button {
                    
                } label: {
                    Text("Very long button 2")
                        .frame(maxWidth: .infinity)
                }
            }
            VStack {
                Button {
                    
                } label: {
                    Text("OK")
                        .frame(maxWidth: .infinity)
                }
                Button {
                    
                } label: {
                    Text("Very long button 2")
                        .frame(maxWidth: .infinity)
                }
            }
        }
        .frame(maxWidth: 200)
    }
}

This produces the following result:

enter image description here

I do not like this behaviour. Funny enough, setting lineLimit to 1 results in truncation of the second button, not in vertical rearrangement.

Does anyone know how this can be solved?


Solution

  • The following two changes get it (half) working:

    However, if the two labels fit in the horizontal layout but one is longer than the other, this can give you two buttons with different widths. For example, this happens if the second label is "Long button 2".

    To make sure the buttons have equal widths, the footprint for the text labels can be established by combining them in a ZStack. Then, the visible labels can be shown as an overlay over the (hidden) footprint.

    Here is the updated example to show it all working. I added a button style, to make it easier to see the bounds of the buttons. A red border is also shown around the resulting combination.

    struct ContentView: View {
        let label1 = "OK"
        let label2 = "Very long button 2"
    
        private var labelFootprint: some View {
            ZStack {
                Text(label1)
                Text(label2)
            }
            .fixedSize(horizontal: true, vertical: false)
            .frame(maxWidth: .infinity)
            .hidden()
        }
    
        var body: some View {
            ViewThatFits(in: .horizontal) {
                HStack {
                    Button {} label: {
                        labelFootprint
                            .overlay { Text(label1) }
                    }
                    Button {} label: {
                        labelFootprint
                            .overlay { Text(label2) }
                    }
                }
                VStack {
                    Button {} label: {
                        Text(label1)
                            .frame(maxWidth: .infinity)
                    }
                    Button {} label: {
                        Text(label2)
                            .frame(maxWidth: .infinity)
                    }
                }
            }
            .buttonStyle(.bordered)
            .frame(maxWidth: 200)
            .padding(4)
            .border(.red)
        }
    }
    

    Screenshot