SwiftUI: 数字键盘布局

8

我正在使用Swift UI构建自定义数字键盘。我希望按钮以填满屏幕的网格方式排列。到目前为止,我发现唯一的方法是使用GeometryReader,但这似乎对于这个简单的任务来说有些过于复杂。有更好的编写方式吗?

GeometryReader { geometry in
    HStack(spacing: 0) {
        ForEach([a, b, c]) { n in
            Button(action: { self.add(n) }) { Text("\(n)") }
                .frame(width: geometry.size.width/3)
        }
    }
}.frame(height: 80)

这导致了这样的结果,这正是我想要的外观。只是好奇是否有一种不用 GeometryReader 的好方法来做到这一点。

输入图像描述


1
你为什么要自定义构建它,而不是使用带有数字键盘类型的文本字段? - Curiosity
2
我想要一个定制的设计 :) - keegan3d
1个回答

27

是的,你可以不使用 GeometryReader 来实现这个功能。你只需要使每个关键视图可扩展即可。然后你的 HStackVStack 就会自动处理剩下的部分。

Button 紧密包裹它的标签子视图,所以你需要使标签子视图可扩展。一个 Color 是一个会扩展以填充其给定空间的 View,所以我们可以使用 Color.clear 作为按钮的标签,并在 Color 上叠加真正的 Text 标签。我认为我们应该为此定义一个 KeyPadButton View

struct KeyPadButton: View {
    var key: String

    var body: some View {
        Button(action: { self.action(self.key) }) {
            Color.clear
                .overlay(RoundedRectangle(cornerRadius: 12)
                    .stroke(Color.accentColor))
                .overlay(Text(key))
        }
    }

    enum ActionKey: EnvironmentKey {
        static var defaultValue: (String) -> Void { { _ in } }
    }

    @Environment(\.keyPadButtonAction) var action: (String) -> Void
}

extension EnvironmentValues {
    var keyPadButtonAction: (String) -> Void {
        get { self[KeyPadButton.ActionKey.self] }
        set { self[KeyPadButton.ActionKey.self] = newValue }
    }
}

#if DEBUG
struct KeyPadButton_Previews: PreviewProvider {
    static var previews: some View {
        KeyPadButton(key: "8")
            .padding()
            .frame(width: 80, height: 80)
            .previewLayout(.sizeThatFits)
    }
}
#endif

我已经定义了一个 EnvironmentKey,这样我们就可以使用环境将操作回调从更高级别的键盘视图传递给所有按钮。

KeyPadButton 看起来像这样:

KeyPadButton 预览

如果您不喜欢边框,请删除该修饰符。

请注意,我在 PreviewProvider 中手动设置了按钮的大小。由于此视图是可扩展的,默认情况下它会以设备大小预览,我们不需要看到一个巨大的按钮。

现在让我们定义一个 KeyPadRow 视图来布置一行按钮:

struct KeyPadRow: View {
    var keys: [String]

    var body: some View {
        HStack {
            ForEach(keys, id: \.self) { key in
                KeyPadButton(key: key)
            }
        }
    }
}

现在我们可以定义KeyPad视图来布局整个键盘并为按钮提供操作:

struct KeyPad: View {
    @Binding var string: String

    var body: some View {
        VStack {
            KeyPadRow(keys: ["1", "2", "3"])
            KeyPadRow(keys: ["4", "5", "6"])
            KeyPadRow(keys: ["7", "8", "9"])
            KeyPadRow(keys: [".", "0", "⌫"])
        }.environment(\.keyPadButtonAction, self.keyWasPressed(_:))
    }

    private func keyWasPressed(_ key: String) {
        switch key {
        case "." where string.contains("."): break
        case "." where string == "0": string += key
        case "⌫":
            string.removeLast()
            if string.isEmpty { string = "0" }
        case _ where string == "0": string = key
        default: string += key
        }
    }
}

最后,让我们定义一个ContentView来显示键盘上面的字符串:

struct ContentView : View {
    var body: some View {
        VStack {
            HStack {
                Spacer()
                Text(string)
            }.padding([.leading, .trailing])
            Divider()
            KeyPad(string: $string)
        }
        .font(.largeTitle)
            .padding()
    }

    @State private var string = "0"

}


#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
        }
    }
}
#endif

这是最终结果:

演示


哇,这是一些高级的SwiftUI!我需要一点时间来仔细研究 :) - keegan3d
这篇文章有很多优质内容,非常感谢!似乎 Color.clear.overlay 作为按钮内容是我最初问题中缺失的重要部分。 - keegan3d
你能推荐一些好的资源来学习EnvironmentValues是如何工作的,以便像这样做事情,并了解它是如何工作的吗? - keegan3d
目前的SwiftUI版本似乎无法直接圆角边框,而需要使用.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.accentColor))来实现相同的效果。 - John
你是怎么把 SF 符号“delete left”作为字符串复制到你的键盘行定义中的? - GrandSteph
我没有。那是一个标准的Unicode字符,U+232B向左擦除。 - rob mayoff

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