SwiftUI:数字键盘布局

SwiftUI: Number pad layout

我正在 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.

的好方法

是的,没有 GeometryReader 也可以做到这一点。您只需要使每个关键视图都可扩展即可。然后你的 HStackVStack 会处理剩下的事情。

一个Button紧紧包裹着它的label subview,所以你需要让label subview可以展开。 Color 是一个 View,它会扩展以填充给定的任何 space,所以让我们使用 Color.clear 作为按钮的标签,并将真正的 Text 标签覆盖在Color。我认为我们应该为此定义一个 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 以便我们可以使用环境将操作回调从 higher-level 键盘视图传递到所有按钮。

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

这是最终结果: