NumberTextField.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. //
  2. // Copyright © 2020 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import SwiftUI
  17. struct NumberTextFieldOld: View {
  18. private var titleKey: LocalizedStringKey
  19. @Binding private var number: NSNumber?
  20. private var promptKey: LocalizedStringKey
  21. private var onEditingChanged: (Bool) -> Void
  22. private let formatter: NumberFormatter
  23. init(_ titleKey: LocalizedStringKey, number: Binding<NSNumber?>, prompt: LocalizedStringKey, onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
  24. self.titleKey = titleKey
  25. self._number = number
  26. self.onEditingChanged = onEditingChanged
  27. self.formatter = NumberFormatter()
  28. self.formatter.usesGroupingSeparator = false
  29. self.formatter.usesSignificantDigits = false
  30. self.promptKey = prompt
  31. }
  32. var body: some View {
  33. HStack {
  34. Text(titleKey)
  35. Spacer()
  36. TextField(promptKey, text: Binding<String>(get: { () -> String in
  37. guard let number = number, let string = formatter.string(from: number) else {
  38. return ""
  39. }
  40. return number.intValue == 0 ? "" : string
  41. }, set: {
  42. // make sure we never set nil
  43. self.number = self.formatter.number(from: $0) ?? 0
  44. }), onEditingChanged: onEditingChanged)
  45. .keyboardType(.numberPad)
  46. .multilineTextAlignment(.trailing)
  47. }
  48. }
  49. }
  50. @available(iOS 15, macOS 12, *)
  51. struct NumberTextFieldNew: View {
  52. private var titleKey: LocalizedStringKey
  53. @Binding private var number: NSNumber?
  54. private var promptKey: LocalizedStringKey
  55. private var onEditingChanged: (Bool) -> Void
  56. // Due to FB9581726 we cannot make `focused` available only on newer APIs.
  57. // Therefore we have to mark the availability on the entire struct.
  58. @FocusState private var focused: Bool
  59. init(_ titleKey: LocalizedStringKey, number: Binding<NSNumber?>, prompt: LocalizedStringKey, onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
  60. self.titleKey = titleKey
  61. self._number = number
  62. self.onEditingChanged = onEditingChanged
  63. self.promptKey = prompt
  64. }
  65. var body: some View {
  66. Group {
  67. #if os(macOS)
  68. TextField(titleKey, value: $number, format: NSNumber.StringFormatStyle(), prompt: Text(promptKey))
  69. .focused($focused)
  70. #else
  71. HStack {
  72. Text(titleKey)
  73. Spacer()
  74. TextField(titleKey, value: $number, format: NSNumber.StringFormatStyle(), prompt: Text(promptKey))
  75. .focused($focused)
  76. }
  77. #endif
  78. }.keyboardType(.numberPad)
  79. .onChange(of: number) { _ in
  80. onEditingChanged(focused)
  81. }
  82. .onSubmit {
  83. focused = false
  84. onEditingChanged(false)
  85. }
  86. .multilineTextAlignment(.trailing)
  87. }
  88. }
  89. struct NumberTextField: View {
  90. private var titleKey: LocalizedStringKey
  91. @Binding private var number: NSNumber?
  92. private var promptKey: LocalizedStringKey
  93. private var onEditingChanged: (Bool) -> Void
  94. init(_ titleKey: LocalizedStringKey, number: Binding<NSNumber?>, prompt: LocalizedStringKey = "0", onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
  95. self.titleKey = titleKey
  96. self._number = number
  97. self.onEditingChanged = onEditingChanged
  98. self.promptKey = prompt
  99. }
  100. init(_ titleKey: LocalizedStringKey, number: Binding<Int?>, prompt: LocalizedStringKey = "0", onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
  101. let nsnumber = Binding<NSNumber?> {
  102. return number.wrappedValue as NSNumber?
  103. } set: { newValue in
  104. number.wrappedValue = newValue?.intValue
  105. }
  106. self.init(titleKey, number: nsnumber, prompt: prompt, onEditingChanged: onEditingChanged)
  107. }
  108. init(_ titleKey: LocalizedStringKey, number: Binding<Int>, prompt: LocalizedStringKey = "0", onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
  109. let nsnumber = Binding<NSNumber?> {
  110. return number.wrappedValue as NSNumber
  111. } set: { newValue in
  112. number.wrappedValue = newValue?.intValue ?? 0
  113. }
  114. self.init(titleKey, number: nsnumber, prompt: prompt, onEditingChanged: onEditingChanged)
  115. }
  116. var body: some View {
  117. if #available(iOS 15, macOS 12, *) {
  118. NumberTextFieldNew(titleKey, number: $number, prompt: promptKey, onEditingChanged: onEditingChanged)
  119. } else {
  120. NumberTextFieldOld(titleKey, number: $number, prompt: promptKey, onEditingChanged: onEditingChanged)
  121. }
  122. }
  123. }
  124. @available(iOS 15, macOS 12, *)
  125. extension NSNumber {
  126. struct StringFormatStyle: ParseableFormatStyle {
  127. var parseStrategy: StringParseStrategy {
  128. return StringParseStrategy()
  129. }
  130. func format(_ value: NSNumber) -> String {
  131. let formatter = NumberFormatter()
  132. formatter.usesGroupingSeparator = false
  133. formatter.usesSignificantDigits = false
  134. let string = formatter.string(from: value) ?? ""
  135. return value.intValue == 0 ? "" : string
  136. }
  137. }
  138. struct StringParseStrategy: ParseStrategy {
  139. func parse(_ value: String) throws -> NSNumber {
  140. let formatter = NumberFormatter()
  141. formatter.usesGroupingSeparator = false
  142. formatter.usesSignificantDigits = false
  143. return formatter.number(from: value) ?? 0
  144. }
  145. }
  146. }
  147. struct NumberTextField_Previews: PreviewProvider {
  148. static var previews: some View {
  149. NumberTextField("Test", number: .constant(123 as NSNumber))
  150. }
  151. }