Home > Enterprise >  SwiftUI making buttons closer to each other horizontally in an HStack
SwiftUI making buttons closer to each other horizontally in an HStack

Time:11-04

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

CodePudding user response:

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) { row in
                let num = KeyboardStack[row].indices
                HStack(spacing: 0) {
                    ForEach(num) { column in
                        Button(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)
                                                })
                    }
                }.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

CodePudding user response:

Here is a refactored code and way for you:,

PS: There is still lot lots of room to make more refactored, like getting rid of hard code values and making keys and keyboard sizes dynamic depending on screen size and so on like support for other languages or small or capital words, adding sound to key press the list can go on and on. Here is a simple way for you to start or use this way for your need. That is why there is a big team working in apple just for keyboard, it seems simple thing but the keyboard in iOS that we are using it is more complex than we know it is an app in our app, when we need type something through keyboard.

enter image description here

enter image description here

struct KeyboardView: View {

    let keyBackgroundColor: Color
    let keyForegroundColor: Color
    let keyboardBackgroundColor: Color

    init(keyBackgroundColor: Color, keyForegroundColor: Color, keyboardBackgroundColor: Color) {
        self.keyBackgroundColor = keyBackgroundColor
        self.keyForegroundColor = keyForegroundColor
        self.keyboardBackgroundColor = keyboardBackgroundColor
    }
    
    init() {
        self.keyBackgroundColor = Color.gray
        self.keyForegroundColor = Color.primary
        self.keyboardBackgroundColor = Color.gray.opacity(0.5)
    }

    private let keyboard: [[String]] = [["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: 5.0) {
            
            ForEach(keyboard.indices) { row in
                let num = keyboard[row].indices
                
                HStack(spacing: 5.0) {
                    
                    ForEach(num) { column in
                        
                        keyBackgroundColor.cornerRadius(2.0)
                            .frame(width: 24.0, height: 24.0)
                            .overlay( Text(keyboard[row][column])
                                        .font(.system(size: 15.0, weight: .regular, design: .default))
                                        .foregroundColor(keyForegroundColor)
                                        .fixedSize() )
                            .onTapGesture { print(keyboard[row][column]) }
                        
                    }
                }
                
            }
        }
        .padding(5.0)
        .background(keyboardBackgroundColor.cornerRadius(5.0))
        
    }
}

use case with some custom code:

enter image description here

enter image description here

enter image description here

enter image description here

CodePudding user response:

I would recommend to use Button as only a wrapper view, build your button's content instead of using string directly. You can control its layout easily.

VStack(spacing: 4) {
    ForEach(keys.indices) { row in
        HStack(spacing: 3) {
            ForEach(keys[row].indices) { column in
                Button {
                    print("Tap \(keys[row][column])")
                } label: {
                    Text(keys[row][column])
                        .font(.system(size: fontSize))
                        .foregroundColor(.white)
                        .frame(minWidth: fontSize)
                        .padding(3)
                        .background(
                            RoundedRectangle(cornerRadius: 2)
                                .fill(.black)
                        )
                }
            }
        }
    }
}

Result

enter image description here

  • Related