mikeCenters / NumberTextField

A powerful SwiftUI text field that handles formatting and retaining number values.
MIT License
18 stars 1 forks source link

NumberTextField

Author: Mike Centers

A powerful SwiftUI text field that handles formatting and retaining number values.

GitHub

License

Overview:

NumberTextField is package for SwiftUI that offers live formatting of a text field. Rather than accepting a string for binding, NumberTextField requires an optional Decimal binding. This allows the developer to only worry about the underlying value of the text field.

Requirements:

macOS(v12), iOS(v15), tvOS(v15), watchOS(v8)

* Still under review. iOS building/testing at this time. *

Usage:

struct ContentView: View {
    var numberFormatter: NumberFormatter {
        let f = NumberFormatter()
        /*
         Setup your formatter.
         */
        f.numberStyle = .percent
        f.maximumFractionDigits = 5
        return f
    }

    @State var value: Decimal? =  1.5

    var body: some View {
        NumberTextField("Enter here...", value: self.$value,
                        formatter: self.numberFormatter,     
                        isActive: self.$textFieldIsActive,
                        onChange: { num in
                            // num is a Decimal? type
                        },
                        onCommit: { num in
                            // num is a Decimal? type
                        })
            .inputAccessory {
                HStack {
                    Spacer()
                    Button(action: { submit() }) {
                        Text("Submit")
                    }
                }
                .padding()
                .background(Color(.secondarySystemBackground))
            }
    }
}

Value

The value parameter for the NumberTextField is a binding to an optional Decimal type. This binding is always updated to the current text field change. The value can also have a value assigned prior to binding to the NumberTextField, for if the developer wants the user to update the value.

value = nil

NumberFormatter

The NumberTextField requires a NumberFormatter to operate properly. This parameter allows customization of how numbers are to be displayed and emitted. However the NumberFormatter is setup, the NumberTextField should always respect the attributes set via the developer.

Formatter Setup

var numberFormatter: NumberFormatter {
    let f = NumberFormatter()

    /*
     It is recommended to set the following parameters for every formatter.
     These can be of whatever value you choose, just make sure they are set.
     */
    f.numberStyle = .decimal        // Set the style first.
    f.minimumFractionDigits = 3
    f.maximumFractionDigits = 7

    return f
}

Formatter Attributes

NumberFormatter.numberStyle

Percent

The NumberTextField expects the value to be in a decimal format.

Fractional Digits

These attributes have a default value unique to the NumberFormatter.numberStyle attribute. If fractional input is not performing as expected, set the .maximumFractionDigits and .minimumFractionDigits attributes to the desired output.

NumberFormatter.alwaysShowDecimalSeparator

The .alwaysShowDecimalSeparator attribute is manipulated via the Coordinator. If the developer chooses to not allow fractional input, set the .maximumFractionalDigits attribute to zero. This will also filter the decimal separator from user input.

Keyboard

When using the .keyboardType modifier of a View, the UIKit text field will not receive the modification. The KeyboardType assigned to the text field is the .decimalPad. There will be no button to call the resignFirstResponder() method. To resolve this, a keyboard accessory must be made within SwiftUI to toggle the state of the text field.

View Modifiers

struct ContentView: View {
    var numberFormatter: NumberFormatter { ... }
    @State var value: Decimal? =  1.5

    var body: some View {
        NumberTextField("Enter here...", value: self.$value,
                        formatter: self.numberFormatter,
                        isActive: self.$textFieldIsActive,
                        onChange: { _ in },
                        onCommit: { _ in })
                    .uiFont(.body, weight: .semibold, design: .rounded)
                    .textColor(self.textFieldIsActive ? .primary : .white)
                    .textAlignment(.center)
    }
}

.uiFont

Set the font to be displayed within the text field.

The modifier will accept a UIFont for complete control of the font. It will also accept dynamic text types, including weight and design modification.

.textColor

Set the text foreground color.

.textAlignment

Set the text alignment.

.inputAccessory

Create an input accessory view that is bound to the keyboard.

Current Issues / Objectives

In no particular order

UI

func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<NumberTextFieldViewRep>) {
    // Insert a check for change in uiView.text
    DispatchQueue.main.async {
        context.coordinator.updateText(uiView)
    }
}

Change Log

v0.3.0

v0.2.2

v0.2.1

v0.2.0

(Current Build: Will Update tag to reflect the upcoming changes.)

v0.1.5

v0.1.4

v0.1.3

Found Bug:

v0.1.2

v0.1.1

v0.1.0