Search code examples
buttonswiftui

Making buttons closer to each other horizontally in an HStack


I tried to make buttons closer to each other horizontally on the keyboard. First I tried adjusting the width of button frame. But I found that if I decrease the frame width, some long-width character like "W" will not show up correctly.

enter image description here

Then I tried making spacing of the HStack to be negative like what I did in the code below.

enter image description here

But this will cause the button overlap with each other and the clickable area will shift to the left which is not acceptable(can be checked by setting background color as blue).

enter image description here

Is there a way to decrease button distance without changing font size?

struct KeyboardView: View {

    let KeyboardStack = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
                          ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
                          ["Z", "X", "C", "V", "B", "N", "M"]]

    var body: some View {
        VStack(spacing: 9) {                      
            ForEach(KeyboardStack.indices) { row in
                let num = KeyboardStack[row].indices
                HStack(spacing: -12) {           
                    ForEach(num) { column in
                        Button(KeyboardStack[row][column]) {}
                        .frame(width: 12, height: 12,alignment: .center)
                        .padding()
//                        .background(Color.blue)
                        .font(.system(size: 15, weight: .regular, design: .default))
                        .foregroundColor(.white)
                        .buttonStyle(PlainButtonStyle())
                    }
                }
                .frame(width: 255, height: 19.5, alignment:.center)
            }
        }
        .frame(width: 445, height: 60, alignment:.center)
    }
}

Solution

  • The first easy thing to do is get rid of your padding() modifier that is adding extra padding to each button.

    I'm assuming, given that it's a keyboard view that you want all of your keys to be the same width. You can use a PreferenceKey to store the maximum width needed to fit a certain letter and then use that for each Button, making it only as large as needed:

    struct KeyboardView: View {
        @State private var keyWidth : CGFloat = 0
        
        let KeyboardStack = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
                             ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
                             ["Z", "X", "C", "V", "B", "N", "M"]]
        
        var body: some View {
            VStack(spacing: 9) {
                ForEach(KeyboardStack.indices, id: \.self) { row in
                    let num = KeyboardStack[row].indices
                    HStack(spacing: 0) {
                        ForEach(num, id: \.self) { column in
                            Button(action: {}) {
                                Text(KeyboardStack[row][column])
                                    .font(.system(size: 15, weight: .regular, design: .default))
                                    .foregroundColor(.white)
                                    .buttonStyle(PlainButtonStyle())
                                    .frame(width: keyWidth)
                                    .background(GeometryReader {
                                        Color.clear.preference(key: KeyWidthKey.self,
                                                               value: $0.frame(in: .local).size.height)
                                    })
                                    .contentShape(Rectangle())
                            }
                        }
                    }.onPreferenceChange(KeyWidthKey.self) { keyWidth = $0 }
                    
                }
            }
        }
    }
    
    struct KeyWidthKey: PreferenceKey {
        static var defaultValue: CGFloat { 0 }
        static func reduce(value: inout Value, nextValue: () -> Value) {
            value = max(value, nextValue())
        }
    }
    

    Note that this solution will continue to work if you change the font size, as it is not dependent on a hard-coded frame size for each key.

    enter image description here