LabeledNumberInput.swift 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. //
  2. // LabeledNumberInput.swift
  3. // LoopKitUI
  4. //
  5. // Created by Nathaniel Hamming on 2020-02-20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. public struct LabeledNumberInput: View {
  10. @Binding var value: Double?
  11. let font: UIFont
  12. let label: String
  13. let placeholder: String
  14. let allowFractions: Bool
  15. let shouldBecomeFirstResponder: Bool
  16. private var numberFormatter: NumberFormatter {
  17. let numberFormatter = NumberFormatter()
  18. numberFormatter.numberStyle = allowFractions ? .decimal : .none
  19. return numberFormatter
  20. }
  21. // seems like the TextField doesn't update the formatted binding until return to tapped. This is the workaround.
  22. private var valueString: Binding<String> {
  23. Binding<String>(
  24. get: { () -> String in
  25. guard let value = self.value else {
  26. return ""
  27. }
  28. return self.numberFormatter.string(from: NSNumber(value: value.rawValue)) ?? ""
  29. },
  30. set: {
  31. if let value = self.numberFormatter.number(from: $0) {
  32. self.value = value.doubleValue
  33. }
  34. }
  35. )
  36. }
  37. public init(value: Binding<Double?>, font: UIFont = .preferredFont(forTextStyle: .largeTitle), label: String, placeholder: String? = nil, allowFractions: Bool = false, shouldBecomeFirstResponder: Bool = false) {
  38. _value = value
  39. self.font = font
  40. self.label = label
  41. self.placeholder = placeholder ?? LocalizedString("Value", comment: "Placeholder text until value is entered")
  42. self.allowFractions = allowFractions
  43. self.shouldBecomeFirstResponder = shouldBecomeFirstResponder
  44. }
  45. public var body: some View {
  46. GeometryReader { geometry in
  47. HStack(alignment: .bottom, spacing: 5) {
  48. DismissibleKeyboardTextField(
  49. text: valueString,
  50. placeholder: placeholder,
  51. font: font,
  52. textAlignment: .right,
  53. keyboardType: allowFractions ? .decimalPad : .numberPad,
  54. shouldBecomeFirstResponder: true,
  55. isDismissible: false
  56. )
  57. .accessibility(label: Text(String(format: LocalizedString("Enter %1$@ value", comment: "Format string for accessibility label for value entry. (1: value label)"), label)))
  58. Text(self.label)
  59. .font(.footnote)
  60. .multilineTextAlignment(.leading)
  61. .foregroundColor(.secondary)
  62. .padding(.bottom, 7)
  63. .frame(width: geometry.size.width/2, alignment: .leading)
  64. }
  65. }
  66. }
  67. }
  68. struct LabeledNumberInput_Previews: PreviewProvider {
  69. static var previews: some View {
  70. LabeledNumberInput(
  71. value: .constant(nil),
  72. label: "mg/dL",
  73. allowFractions: true)
  74. }
  75. }