TextField в SwiftUI: проблема с вводом цифровых значений

swiftui

#1

Добрый день соратники разработчики!

Столкнулся с проблемой работы TextField при использовании со значениями, отличными от текстовых.
Код выглядит так:

struct ContentView: View {
    
    @State var floatNum: Float = 0
    
    var body: some View {
        GroupBox(label: Text("Enter value")) {
            TextField("", value: $floatNum, formatter: NumberFormatter.decimal)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .keyboardType(.decimalPad)
            Text("\(currentFloat)")
        }.modifier(DismissingKeyboard())
    }
}
extension NumberFormatter {
    static var decimal: NumberFormatter {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }
}

Суть проблемы: Если использовать клавиатуру по умолчанию, то нажатие кнопки Return распространяет введенное значение на связанное значение (floatNum), т. е. всё работает без проблем.

А вот у клавиатур типа decimalPad или numberPad кнопка возврата отсутствует. То есть нет штатного способа даже закрыть клавиатуру.

Для закрытия клавиатуры в коде есть модификатор DismissingKeyboard()

struct DismissingKeyboard: ViewModifier {
    func body(content: Content) -> some View {
        content
            .onTapGesture {
                let keyWindow = UIApplication.shared.connectedScenes
                    .filter({$0.activationState == .foregroundActive})
                    .map({$0 as? UIWindowScene})
                    .compactMap({$0})
                    .first?.windows
                    .filter({$0.isKeyWindow}).first
                keyWindow?.endEditing(true)
            }
    }
}

Но вот функцию кнопки Return (кроме закрытия клавиатуры ещё и передать введенные цифры в связанное значение) он не выполняет.

Кто нибудь знает способ как решить эту проблему?


#2

Смотрите внимательнее на существующие инициализаторы штатных объектов

Для TextField есть инициализатор

TextField(title: StringProtocol, text: Binding<String>, onEditingChanged: <(Bool) -> Void>, onCommit: <T() -> Void>)

который реализует 2 клоужера для отработки изменений значение ТФ и конца редактирования.

Используйте поиск - уже писал про это.


#3

Я частично решил эту проблему через .Disappear.

Я использую это при работе с CoreData, поэтому все работает как мне надо.

А на самом деле проблема зарыта глубже, если сможете подсказать - буду благодарен.

Суть в преобразовании типов данных.

Вот кусок кода, описывающий мой алгоритм работы:

    import CoreData
    import SwiftUI

    struct FloatInputView: View {
    @Envirinment(\.managedObjectContext) private var viewContext

    @ObservedObject var currentItem = Item() // модель данных CoreData

    @State var currentFloat: String = ""

    var body: some View {
        TextField("", text: $currentFloat)
        }.modifier(DismissingKeyboard())
        .onAppear{
             //здесь я преобразую данные из CoreData в String для работы с TextField
             currentFloat = "\(currentItem.floatValue)" 
        }
        .onDisappear {
             //Здесь я возвращаю введенные данные в CoreData, преобразуя String обратно в Float
             currentItem.floatValue = NSString(string: currentFloat).floatValue
             do {try viewContext.save()}
             cath {print(error.localizedDescription)}
          }
       }
    }

Так вот и суть проблемы:

Если пользователь ввел значение в виде 123.45, то есть с разделителем дробной части в виде точки, все работает.
А если введено значение в виде 123,45 - с разделителем дробной части запятой, как на клавиатуре .decimalPad для российской локали (получается локализованный формат), то преобразуется и сохраняется только целочисленная часть.

Я специально переводил устройство на локализацию en_USA - в таком режиме на .decimalPad в качестве разделителя вместо запятой - точка, соответственно все работает как надо.

Вот и ломаю голову, как преобразовать String в Float, если разделитель дробной части - запятая.

Адекватного ответа я пока не нашел, хотя перерыл уже все форумы


#4
let enString = currentFloat.replace(",", ".")
currentItem.floatValue = NSString(string: enString).floatValue

сразу говорю, код не скомпилируется, он чисто для примера


#5

Помогло.

Благодарю вас.


#6

обрабатывать данные в “аппирах” ну это совсем так себе.

  • ТФ есть нужный инициализатор
  • есть такая штука как didSet, где вы можете обработать данные перед установкой…
    да вариантов масса.

создавать объект контекста лучше внутри этого контекста
Item(context: viewContext)


#7
TextField("", text: $currentFloat)
    .onChange(of: currentFloat, perform: { value in
      // клоужер срабатывает при любом изменении отслеживаемой переменной
     })

#8

Благодарю за подсказки.

Я так и сделаю. Правда у меня больше половины значений (уже больше двух сотен) в CoreData хранятся в виде Float и Int, поэтому подгружать их в представления всё равно придётся через .onAppear