Skip to content

Commit fdaa3e3

Browse files
committed
Make a ViewModifier that does the numeric filtering
1 parent ebe4515 commit fdaa3e3

3 files changed

Lines changed: 54 additions & 13 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Foundation
2+
3+
var decimalNumberFormatter: NumberFormatter = {
4+
let formatter = NumberFormatter()
5+
formatter.numberStyle = .decimal
6+
return formatter
7+
}()

Sources/NumericText/NumericTextField.swift

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ public struct NumericTextField: View {
77
@Binding private var number: NSNumber?
88
@State private var string: String
99
private let isDecimalAllowed: Bool
10-
private let formatter: NumberFormatter = NumberFormatter()
1110

1211
private let title: LocalizedStringKey
1312
private let onEditingChanged: (Bool) -> Void
@@ -24,9 +23,8 @@ public struct NumericTextField: View {
2423
/// The closure receives a Boolean indicating whether the text field is currently being edited.
2524
/// - onCommit: An action to perform when the user performs an action (for example, when the user hits the return key) while the text field has focus.
2625
public init(_ titleKey: LocalizedStringKey, number: Binding<NSNumber?>, isDecimalAllowed: Bool, onEditingChanged: @escaping (Bool) -> Void = { _ in }, onCommit: @escaping () -> Void = {}) {
27-
formatter.numberStyle = .decimal
2826
_number = number
29-
if let number = number.wrappedValue, let string = formatter.string(from: number) {
27+
if let number = number.wrappedValue, let string = decimalNumberFormatter.string(from: number) {
3028
_string = State(initialValue: string)
3129
} else {
3230
_string = State(initialValue: "")
@@ -38,18 +36,10 @@ public struct NumericTextField: View {
3836
}
3937

4038
public var body: some View {
41-
return TextField(title, text: $string, onEditingChanged: onEditingChanged, onCommit: onCommit)
42-
.onChange(of: string, perform: numberChanged(newValue:))
39+
TextField(title, text: $string, onEditingChanged: onEditingChanged, onCommit: onCommit)
40+
.numericText(text: $string, number: $number, isDecimalAllowed: isDecimalAllowed)
4341
.modifier(KeyboardModifier(isDecimalAllowed: isDecimalAllowed))
4442
}
45-
46-
private func numberChanged(newValue: String) {
47-
let numeric = newValue.numericValue(allowDecimalSeparator: isDecimalAllowed)
48-
if newValue != numeric {
49-
string = numeric
50-
}
51-
number = formatter.number(from: string)
52-
}
5343
}
5444

5545
private struct KeyboardModifier: ViewModifier {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import SwiftUI
2+
3+
/// A modifier that observes any changes to a string, and updates that string to remove any non-numeric characters.
4+
/// It also will convert that string to a `NSNumber` for easy use.
5+
public struct NumericTextModifier: ViewModifier {
6+
/// Should the user be allowed to enter a decimal number, or an integer
7+
public let isDecimalAllowed: Bool
8+
/// The string that the text field is bound to
9+
@Binding public var text: String
10+
/// A number that will be updated when the `text` is updated.
11+
@Binding public var number: NSNumber?
12+
13+
/// A modifier that observes any changes to a string, and updates that string to remove any non-numeric characters.
14+
/// It also will convert that string to a `NSNumber` for easy use.
15+
///
16+
/// - Parameters:
17+
/// - text: The string that this should observe and filter
18+
/// - number: A number that should be updated whenever the `text` is updated
19+
/// - isDecimalAllowed: Should the user be allowed to enter a decimal number, or an integer
20+
public init(text: Binding<String>, number: Binding<NSNumber?>, isDecimalAllowed: Bool) {
21+
_text = text
22+
_number = number
23+
self.isDecimalAllowed = isDecimalAllowed
24+
}
25+
26+
public func body(content: Content) -> some View {
27+
content
28+
.onChange(of: text) { newValue in
29+
let numeric = newValue.numericValue(allowDecimalSeparator: isDecimalAllowed)
30+
if newValue != numeric {
31+
text = numeric
32+
}
33+
number = decimalNumberFormatter.number(from: numeric)
34+
}
35+
}
36+
}
37+
38+
public extension View {
39+
/// A modifier that observes any changes to a string, and updates that string to remove any non-numeric characters.
40+
/// It also will convert that string to a `NSNumber` for easy use.
41+
func numericText(text: Binding<String>, number: Binding<NSNumber?>, isDecimalAllowed: Bool) -> some View {
42+
modifier(NumericTextModifier(text: text, number: number, isDecimalAllowed: isDecimalAllowed))
43+
}
44+
}

0 commit comments

Comments
 (0)