SwiftUI如何在HStack中让按钮水平靠得更近?

3

我试图让键盘上的按钮在水平方向更加接近。首先,我尝试调整按钮框架的宽度。但我发现如果我减小框架的宽度,一些长宽字符如“W”将无法正确显示。

enter image description here

然后我尝试将HStack的间距设置为负数,就像下面的代码一样。

enter image description here

但这会导致按钮重叠并且可点击区域将向左移动,这是不可接受的(可以通过将背景色设置为蓝色进行检查)。

enter image description here

有没有一种方法可以减少按钮之间的距离而不改变字体大小?
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)
    }
}
3个回答

2
第一步容易的事情是去掉你的 padding() 修改器,它会给每个按钮添加额外的填充。
我假设,鉴于它是一个键盘视图,你希望所有的按键宽度都相同。你可以使用一个 PreferenceKey 来存储适合某个字母的最大宽度,然后对每个 Button 使用它,使其只有必要的大小:
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(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())
    }
}

请注意,该解决方案将继续工作,即使您更改字体大小,因为它不依赖于每个键的硬编码frame大小。

enter image description here


谢谢。那肯定有效,但可点击区域也将取决于字符的形状。例如,字符“i”的按钮将变得极难触发。有什么建议吗? - yo1nk
当然可以——添加.contentShape(Rectangle())。我已经更新了答案。 - jnpdx

0

这里有一个经过重构的代码和方法供您参考:

PS:仍然有很多空间可以进行更多的重构,例如摆脱硬编码值并使键和键盘大小根据屏幕大小动态变化,以及支持其他语言或小写或大写字母等等,添加按键声音等等。这是一个简单的开始方式,您可以使用此方式来满足您的需求。 这就是为什么在苹果公司有一个庞大的团队专门负责键盘,它看起来很简单,但我们在iOS中使用的键盘比我们所知道的更加复杂,它是我们应用程序中的一个应用程序,当我们需要通过键盘输入东西时。

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

带有自定义代码的使用案例:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here


如果将每个颜色都设置为具有默认值的“var”而不是“let”,则可以摆脱两个“init”。然后,合成的初始化程序可以传递任何颜色(或不传递)。 - jnpdx
但我的计划是使用Snap键盘用例!就像:KeyboardView()和一个带有初始化的自定义键盘。通常情况下,KeyboardView在应用程序中不需要不断更改颜色,这就是为什么let很适合的原因。 - ios coder
使用带有默认值的 var 将与您现在拥有的完全相同,但只是允许可选使用参数,并且您不必定义 任何一个 init。您仍然可以编写 KeyboardView()KeyboardView(keyForegroundColor: .blue) 等。只需在 struct 的顶部定义属性,例如 var keyBackgroundColor: Color = Color.grayvar keyForegroundColor: Color = Color.primary - jnpdx
1
我明白你的意思,如果按照你所解释的方式进行编码,用户可能会选择与默认背景键相同的前景色,而不知道我已经将该颜色作为默认值。这只是一个失败的例子。在用户想要使用的视图的其他部分中,也可能存在相同的问题,除了另一个或自定义视图之外。我的计划是清楚地向代码用户说明所选择的颜色。 - ios coder

0
我建议使用Button作为包装视图,而不是直接使用字符串来构建按钮的内容。这样你可以轻松地控制它的布局。
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)
                        )
                }
            }
        }
    }
}

结果

enter image description here


Button的唯一用途是提供点击效果,而不仅仅是文本!因为按下的按钮总是在手指下面,所以它永远不会被注意到!iOS键盘唯一的反馈是声音和一些大的字母泡沫!如果你尝试使用iOS键盘,它们不会显示按下的情况。但由于这是自定义代码,您可以随心所欲地做任何事情。 - ios coder
@swiftPunk 我认为他们不会使用SwiftUI构建iOS键盘。问题不在于实现键盘的技术方面 :D - Quang Hà

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接