|
@@ -13,9 +13,6 @@ import HealthKit
|
|
|
public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
@Binding var quantity: HKQuantity
|
|
@Binding var quantity: HKQuantity
|
|
|
|
|
|
|
|
- @State private var alertMessage: String? = nil
|
|
|
|
|
- @State private var showAlert: Bool = false
|
|
|
|
|
-
|
|
|
|
|
var textColor: UIColor
|
|
var textColor: UIColor
|
|
|
var textAlignment: NSTextAlignment
|
|
var textAlignment: NSTextAlignment
|
|
|
var autocapitalizationType: UITextAutocapitalizationType
|
|
var autocapitalizationType: UITextAutocapitalizationType
|
|
@@ -28,6 +25,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
var allowDecimalSeparator: Bool
|
|
var allowDecimalSeparator: Bool
|
|
|
var minValue: HKQuantity?
|
|
var minValue: HKQuantity?
|
|
|
var maxValue: HKQuantity?
|
|
var maxValue: HKQuantity?
|
|
|
|
|
+ var onValidationError: (String) -> Void
|
|
|
|
|
|
|
|
public init(
|
|
public init(
|
|
|
quantity: Binding<HKQuantity>,
|
|
quantity: Binding<HKQuantity>,
|
|
@@ -42,7 +40,8 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
unit: HKUnit,
|
|
unit: HKUnit,
|
|
|
allowDecimalSeparator: Bool = true,
|
|
allowDecimalSeparator: Bool = true,
|
|
|
minValue: HKQuantity? = nil,
|
|
minValue: HKQuantity? = nil,
|
|
|
- maxValue: HKQuantity? = nil
|
|
|
|
|
|
|
+ maxValue: HKQuantity? = nil,
|
|
|
|
|
+ onValidationError: @escaping (String) -> Void
|
|
|
) {
|
|
) {
|
|
|
_quantity = quantity
|
|
_quantity = quantity
|
|
|
self.textColor = textColor
|
|
self.textColor = textColor
|
|
@@ -57,6 +56,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
self.allowDecimalSeparator = allowDecimalSeparator
|
|
self.allowDecimalSeparator = allowDecimalSeparator
|
|
|
self.minValue = minValue
|
|
self.minValue = minValue
|
|
|
self.maxValue = maxValue
|
|
self.maxValue = maxValue
|
|
|
|
|
+ self.onValidationError = onValidationError
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func formattedPlaceholder(for unit: HKUnit) -> String {
|
|
private func formattedPlaceholder(for unit: HKUnit) -> String {
|
|
@@ -77,15 +77,6 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
textField.text = quantity.doubleValue(for: unit) == 0 ? "" : context.coordinator.format(quantity: quantity, for: unit)
|
|
textField.text = quantity.doubleValue(for: unit) == 0 ? "" : context.coordinator.format(quantity: quantity, for: unit)
|
|
|
textField.placeholder = formattedPlaceholder(for: unit)
|
|
textField.placeholder = formattedPlaceholder(for: unit)
|
|
|
textField.keyboardType = unit.preferredFractionDigits == 0 ? .numberPad : .decimalPad
|
|
textField.keyboardType = unit.preferredFractionDigits == 0 ? .numberPad : .decimalPad
|
|
|
-
|
|
|
|
|
- if showAlert {
|
|
|
|
|
- let alert = UIAlertController(title: "Input Error", message: alertMessage, preferredStyle: .alert)
|
|
|
|
|
- alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
|
|
|
|
|
- showAlert = false
|
|
|
|
|
- })
|
|
|
|
|
- UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
return textField
|
|
return textField
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -104,7 +95,6 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
target: context.coordinator,
|
|
target: context.coordinator,
|
|
|
action: #selector(Coordinator.clearText)
|
|
action: #selector(Coordinator.clearText)
|
|
|
)
|
|
)
|
|
|
-
|
|
|
|
|
toolbar.items = [clearButton, flexibleSpace, doneButton]
|
|
toolbar.items = [clearButton, flexibleSpace, doneButton]
|
|
|
toolbar.sizeToFit()
|
|
toolbar.sizeToFit()
|
|
|
return toolbar
|
|
return toolbar
|
|
@@ -134,7 +124,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public func makeCoordinator() -> Coordinator {
|
|
public func makeCoordinator() -> Coordinator {
|
|
|
- Coordinator(self, maxLength: maxLength, unit: unit, minValue: minValue, maxValue: maxValue)
|
|
|
|
|
|
|
+ Coordinator(self, maxLength: maxLength, unit: unit, minValue: minValue, maxValue: maxValue, onValidationError: onValidationError)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public final class Coordinator: NSObject, UITextFieldDelegate {
|
|
public final class Coordinator: NSObject, UITextFieldDelegate {
|
|
@@ -146,13 +136,15 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
var unit: HKUnit
|
|
var unit: HKUnit
|
|
|
var minValue: HKQuantity?
|
|
var minValue: HKQuantity?
|
|
|
var maxValue: HKQuantity?
|
|
var maxValue: HKQuantity?
|
|
|
|
|
+ let onValidationError: (String) -> Void
|
|
|
|
|
|
|
|
- init(_ parent: TextFieldWithToolBar, maxLength: Int?, unit: HKUnit, minValue: HKQuantity?, maxValue: HKQuantity?) {
|
|
|
|
|
|
|
+ init(_ parent: TextFieldWithToolBar, maxLength: Int?, unit: HKUnit, minValue: HKQuantity?, maxValue: HKQuantity?, onValidationError: @escaping (String) -> Void) {
|
|
|
self.parent = parent
|
|
self.parent = parent
|
|
|
self.maxLength = maxLength
|
|
self.maxLength = maxLength
|
|
|
self.unit = unit
|
|
self.unit = unit
|
|
|
self.minValue = minValue
|
|
self.minValue = minValue
|
|
|
self.maxValue = maxValue
|
|
self.maxValue = maxValue
|
|
|
|
|
+ self.onValidationError = onValidationError
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@objc fileprivate func clearText() {
|
|
@objc fileprivate func clearText() {
|
|
@@ -192,72 +184,12 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
|
|
|
if let maxValue = self.parent.maxValue {
|
|
if let maxValue = self.parent.maxValue {
|
|
|
message += "Maximum: \(self.format(quantity: maxValue, for: self.unit))"
|
|
message += "Maximum: \(self.format(quantity: maxValue, for: self.unit))"
|
|
|
}
|
|
}
|
|
|
- self.showInvalidInputAlert(textField, message: message)
|
|
|
|
|
|
|
+ self.onValidationError(message)
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- self.showInvalidInputAlert(textField, message: "Invalid number format")
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func showInvalidInputAlert(_ textField: UITextField, message: String) {
|
|
|
|
|
- var message = "Value outside of guardrails\n"
|
|
|
|
|
- let preferredFractionDigits = self.unit.preferredFractionDigits
|
|
|
|
|
- let roundingFactor = pow(10.0, Double(preferredFractionDigits))
|
|
|
|
|
-
|
|
|
|
|
- if let minValue = self.parent.minValue {
|
|
|
|
|
- let minValueValue = minValue.doubleValue(for: self.unit)
|
|
|
|
|
- let minValueRoundedUp = ceil(minValueValue * roundingFactor) / roundingFactor
|
|
|
|
|
- message += "Minimum: \(self.format(quantity: HKQuantity(unit: self.unit, doubleValue: minValueRoundedUp), for: self.unit))\n"
|
|
|
|
|
- }
|
|
|
|
|
- if let maxValue = self.parent.maxValue {
|
|
|
|
|
- let maxValueValue = maxValue.doubleValue(for: self.unit)
|
|
|
|
|
- let maxValueRoundedDown = floor(maxValueValue * roundingFactor) / roundingFactor
|
|
|
|
|
- message += "Maximum: \(self.format(quantity: HKQuantity(unit: self.unit, doubleValue: maxValueRoundedDown), for: self.unit))"
|
|
|
|
|
- }
|
|
|
|
|
- let alert = UIAlertController(title: "Input Validation Error", message: message, preferredStyle: .alert)
|
|
|
|
|
- alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
|
|
|
|
|
- textField.becomeFirstResponder()
|
|
|
|
|
- })
|
|
|
|
|
- alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
|
|
|
|
- })
|
|
|
|
|
- UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public func textField(
|
|
|
|
|
- _ textField: UITextField,
|
|
|
|
|
- shouldChangeCharactersIn range: NSRange,
|
|
|
|
|
- replacementString string: String
|
|
|
|
|
- ) -> Bool {
|
|
|
|
|
- let decimalSeparator = Locale.current.decimalSeparator ?? "."
|
|
|
|
|
- let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
|
|
|
|
|
- let isDecimalSeparator = (string == decimalSeparator && textField.text?.contains(decimalSeparator) == false)
|
|
|
|
|
-
|
|
|
|
|
- let currentText = textField.text ?? ""
|
|
|
|
|
- let proposedText = (currentText as NSString).replacingCharacters(in: range, with: string)
|
|
|
|
|
-
|
|
|
|
|
- if let maxLength = maxLength, proposedText.count > maxLength {
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let isValidInput = isNumber || (isDecimalSeparator && parent.allowDecimalSeparator && unit.preferredFractionDigits > 0)
|
|
|
|
|
-
|
|
|
|
|
- if isValidInput, let number = Double(proposedText.replacingOccurrences(of: decimalSeparator, with: ".")) {
|
|
|
|
|
- let quantity = HKQuantity(unit: unit, doubleValue: number)
|
|
|
|
|
- if isWithinLimits(quantity) {
|
|
|
|
|
- parent.quantity = quantity
|
|
|
|
|
- } else {
|
|
|
|
|
- parent.quantity = HKQuantity(unit: unit, doubleValue: 0)
|
|
|
|
|
|
|
+ self.onValidationError("Invalid number format")
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- parent.quantity = HKQuantity(unit: unit, doubleValue: 0)
|
|
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return isValidInput
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public func textFieldDidBeginEditing(_: UITextField) {
|
|
|
|
|
- parent.textFieldDidBeginEditing?()
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func format(quantity: HKQuantity, for unit: HKUnit) -> String {
|
|
func format(quantity: HKQuantity, for unit: HKUnit) -> String {
|