【问题标题】:SwiftUI making buttons closer to each other horizontally in an HStackSwiftUI 使 HStack 中的按钮在水平方向上彼此靠近
【发布时间】:2021-12-18 04:36:42
【问题描述】:

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

然后我尝试将 HStack 的间距设置为负值,就像我在下面的代码中所做的那样。

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

有没有办法在不改变字体大小的情况下减少按钮距离?

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

【问题讨论】:

    标签: swift button swiftui


    【解决方案1】:

    要做的第一件简单的事情是去掉你的 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 大小。

    【讨论】:

    • 谢谢。这肯定行得通,但可点击区域也将取决于字符的形状。例如,字符“i”的按钮将变得极难触发。有什么建议吗?
    • 当然——添加.contentShape(Rectangle())。我已经更新了答案
    【解决方案2】:

    我建议将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)
                            )
                    }
                }
            }
        }
    }
    

    结果

    【讨论】:

    • 按钮的唯一用例,而不只是文本,被赋予了点击效果!由于按下的按钮总是在手指下,它永远不会被注意到! iOS 键盘的唯一反馈是声音和一些大字母!如果您尝试使用 iOS 键盘,它们不会显示按下状态。但由于它是一个自定义代码,你可以做任何你喜欢的事情
    • @swiftPunk 我不认为他们用 swiftui 构建 ios 键盘。问题不在于实现键盘的技术:D
    【解决方案3】:

    这是一个重构的代码和方法:,

    PS:还有很多空间可以进行更多重构,例如摆脱硬代码值并使键和键盘大小根据屏幕大小动态变化等等,例如支持其他语言或小型或大写话,添加声音到按键列表可以继续下去。这是一种简单的方法,您可以根据需要开始或使用这种方法。 这就是为什么苹果有一个大团队只为键盘工作,这看起来很简单,但是我们使用的 iOS 中的键盘比我们知道的要复杂得多,因为它是我们应用程序中的一个应用程序,当我们需要通过键盘输入一些东西时.

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

    一些自定义代码的用例:

    【讨论】:

    • 如果您将每种颜色设置为 var 而不是 let 并使用默认值,则可以同时删除 inits。然后,合成的初始化程序可以传入(或不传入)任何颜色。
    • 但我的计划是拥有快速键盘用例!就像:KeyboardView() 和一个带有初始化的自定义!正常情况下,KeyboardView 不需要在 app 中一次次改变颜色,所以 let 可以很好的适配。
    • var 与默认值一起使用将与您现在所拥有的完全相同,但只是允许它是可选的以使用参数并且您不必定义 init。你仍然可以写KeyboardView(),或KeyboardView(keyForegroundColor: .blue)等。只需定义struct顶部的属性,如var keyBackgroundColor: Color = Color.grayvar keyForegroundColor: Color = Color.primary
    • 我知道你的意思,如果我按照你解释的方式制作,代码用户可以为前景选择相同的颜色,而不知道我使用该颜色作为背景键的默认颜色。这只是一个失败的例子。在用户希望在另一个视图旁边或自定义视图上使用的视图的其他部分中,这可能是相同的问题。我的计划是让代码用户清楚地了解所选择的颜色。
    猜你喜欢
    • 2014-09-14
    • 2018-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 1970-01-01
    相关资源
    最近更新 更多