I'm building a custom number pad in Swift UI. I want the buttons to be arranged in a grid that fills the screen. The only way I've found to do this for far is to use GeometryReader
but this seems like overkill for this simple task. Is there a better way to write this?
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)
This results in this, which is how I want it to look. Just curious if there is a good way to do this without GeometryReader
.
Yes, you can do this without GeometryReader
. You just have to make each key view expandable. Then your HStack
and VStack
will take care of the rest.
A Button
tightly wraps its label subview, so you need to make the label subview expandable. A Color
is a View
that expands to fill whatever space it's given, so let's use Color.clear
as the button's label, and overlay the real Text
label on the Color
. I think we should define a KeyPadButton
View
for this:
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
I've defined an EnvironmentKey
so that we can use the environment to pass the action callback from the higher-level keypad view to all the buttons.
KeyPadButton
looks like this:
If you don't like the border, just remove that modifier.
Note that I've manually set the size of the button in the PreviewProvider
. Since this view is expandable, it'll preview at the device size by default, and we don't need to see one giant button.
Now let's define a KeyPadRow
view to lay out one row of buttons:
struct KeyPadRow: View {
var keys: [String]
var body: some View {
HStack {
ForEach(keys, id: \.self) { key in
KeyPadButton(key: key)
}
}
}
}
Now we can define the KeyPad
view to lay out the entire keypad and provide the action for the buttons:
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
}
}
}
Finally, let's define a ContentView
that shows the string above the keypad:
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
Here's the final result:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With