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.
Then I tried making spacing of the HStack to be negative like what I did in the code below.
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).
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.
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.
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:
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