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

Предположим, у вас есть объект SwiftData, который не допускает дополнительных параметров:

@Model
class MySet: Identifiable {
    var id: UUID
    var weight: Int
    var reps: Int
    var isCompleted: Bool
    var exercise: Exercise

Затем вы получаете из данных MySet список этих MySet и добавляете их в состояние:

  @State var sets: [MySet]

            if let newSets = exercise.sets {    //unwrapping the passed exercises sets
                if (!newSets.isEmpty) {     //we passed actual sets in so set them to our set
                    sets = newSets
                }

И вы используете этот список состояний в ForEach и привязываете текстовое поле непосредственно к данным следующим образом:

 ForEach($sets){ $set  in

 TextField("\(set.weight)", value: $set.weight, formatter: NumberFormatter())
                                        .keyboardType(.decimalPad)

   TextField("\(set.reps)", value: $set.reps,formatter: NumberFormatter())
                                        .keyboardType(.decimalPad)
                                    


}

Это свяжет все, что записано в TextField, непосредственно с объектом SwiftData, что является проблемой, поскольку, скажем, у вас записано 50, и вы хотите изменить его на 60, вам нужно иметь возможность очистить 50, чтобы записать 60, и поскольку объект SwiftData не разрешает ноль, он позволит вам удалить 0, но не 5. Есть ли способ исправить это, не разрешая объекту SwiftData дополнительные опции, например, перехватывать очистку TextField и делать его 0 вместо нуля, когда он очищается?

Я пытаюсь исправить это, привязав TextField к новому состоянию и используя onChanged:

@State private var reps: Int = 0
@State private var weight: Int = 0

                            TextField("\(myset.weight)", value: $weight, formatter: NumberFormatter())
                                .keyboardType(.decimalPad)
                             
                                .onChange(of: weight) { newWeight in
                                    // Code to run when propertyToWatch changes to newValue
                                    if newWeight != 0 {
                                        myset.weight = newWeight
                                    }
                                }

но теперь он меняет каждое повторение и вес во всех подходах, когда я печатаю один, а не только тот, который я печатаю

Вы можете привязать TextField к другой переменной, которая может быть необязательной. Затем используйте обработчик .onChange для обновления целевого значения при каждом изменении связанного значения.

Benzy Neez 07.08.2024 13:09

Я пытаюсь это сделать, но теперь оно меняет каждое повторение и вес во всех подходах, когда я ввожу один

john smith 07.08.2024 13:32

И это должно быть .onChange(of: weight) без $.

Joakim Danielson 07.08.2024 13:36

Йоаким, я удалил последний вопрос, потому что я сузил точную причину проблемы, и я прошу вас не комментировать остальные мои вопросы, потому что вы еще не дали реального ответа и комментируете каждый вопрос для всех. делает. С уважением

john smith 07.08.2024 13:38

Вы можете попробовать тот же метод, который описан в моем ответе, здесь: stackoverflow.com/questions/78802510/… Идея состоит в том, чтобы использовать text: в TextField, потому что текст затем постоянно обновляется. Но когда вы используете value:, то есть «Для нестроковых типов оно обновляет значение, когда пользователь фиксирует свои изменения, например, нажимая клавишу Return». Таким образом, вы можете настроить логику в соответствии с вашими требованиями. См. текстовое поле.

workingdog support Ukraine 07.08.2024 14:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я не уверен, почему ваш код не работает, было бы полезно иметь минимальный воспроизводимый пример для работы.

Однако одним из способов решения проблемы было бы создание универсальной оболочки для TextField. Обертка может хранить переменную теневого состояния и выполнять копирование из необязательного в необязательное.

Вот попытка реализовать такую ​​обертку. Я выбрал инициализатор Textfield, который принимает ParseableFormatStyle:

struct ValueField<F>: View
where F: ParseableFormatStyle, F.FormatInput: Equatable, F.FormatOutput == String {
    private let titleKey: LocalizedStringKey
    @Binding private var value: F.FormatInput
    private let format: F
    private let prompt: Text?
    @State private var fieldVal: F.FormatInput?
    @FocusState private var isFocused: Bool

    init(
        _ titleKey: LocalizedStringKey,
        value: Binding<F.FormatInput>,
        format: F,
        prompt: Text? = nil
    ) {
        self.titleKey = titleKey
        self._value = value
        self.format = format
        self.prompt = prompt
    }

    var body: some View {
        TextField(titleKey, value: $fieldVal, format: format, prompt: prompt)
            .focused($isFocused)
            .onChange(of: fieldVal) { oldVal, newVal in
                if let newVal, newVal != value {
                    value = newVal
                }
            }
            .onChange(of: isFocused, initial: true) { oldVal, newVal in
                if newVal {
                    fieldVal = nil
                } else if fieldVal == nil {
                    fieldVal = value
                }
            }
    }
}

Пример использования:

struct ContentView: View {
    @State private var val: Int = 0

    var body: some View {
        VStack(spacing: 40) {
            ValueField(
                "Weight",
                value: $val,
                format: .number,
                prompt: Text("Weight")
            )
            .keyboardType(.decimalPad)

            TextField("Dummy", text: dummy)
        }
        .textFieldStyle(.roundedBorder)
        .padding()
    }

    private var dummy: Binding<String> {
        Binding<String>(
            get: { "\(val)" },
            set: { _ in /* NOP */ }
        )
    }
}

Animation

onChange предназначен для внешних действий, это не лучший инструмент для преобразования состояния, для этого лучше подходят вычисляемые переменные
malhal 07.08.2024 16:42

@malhal Я не понимаю, как любой из случаев .onChange в ответе можно заменить вычисленными переменными и при этом работать так же (с немедленными обновлениями в ответ на действия). Не могли бы вы уточнить?

Benzy Neez 07.08.2024 16:49

@malhal Я видел, как вы использовали вычисленную привязку в своем недавнем ответе, и вижу, как это можно применить к примеру использования, ответ обновлен. В примере второй TextField использовался просто как способ переключения фокуса, так что это не имело большого значения. Но я до сих пор не понимаю, как даже вычисленную привязку можно использовать в структуре-оболочке. Пожалуйста, просветите меня.

Benzy Neez 07.08.2024 18:42

Вычисляемая привязка предназначена для преобразования типов статистики для такого рода предельной длины. DidSet может быть лучше.

malhal 07.08.2024 20:56

Вы можете создать собственную привязку, которая должна помочь:

private func weightBinding(for set: MySet) -> Binding<Int?> {
    Binding<Int?>(
         get: { set.weight },
         set: { value in
             if let value {
                set.weight = value
             }
          }
      )
}

ForEach(sets) { set in
   TextField("\(set.weight)", value: weightBinding(for: set), formatter: NumberFormatter())
}

// or as extension if you prefer:
extension MySet {
    var weightBinding: Binding<Int?> {
        Binding<Int?>(
            get: { self.weight },
            set: { value in
                if let value {
                    self.weight = value
                }
            }
        )
    }
}

TextField("\(set.weight)", value: set.weightBinding, formatter: NumberFormatter())

Таким образом вы можете очистить текстовое поле, но если вы не введете новое значение и не прекратите редактирование, оно будет использовать последнее значение.

Обновлено:

Если вы хотите установить значение 0, когда текстовое поле пусто, вам нужно изменить привязку на это:


 Binding<Int?>(
         get: { set.weight },
         set: { value in
             self.weight = value ?? 0
          }
      )

это почти работает, но если вес был 60, и я удаляю его и нажимаю на другое текстовое поле, последнее значение возвращается к 6 вместо 60

john smith 07.08.2024 15:57

Нет, я имею в виду, что было 60, и я удалил 60 и ничего не вводил, затем я щелкнул по другому текстовому полю, и вместо возврата к 60 оно переходит к 6, поэтому эта реализация не работает

john smith 07.08.2024 16:07

Как ты убрал 60? значение 60 -> удалить 0 -> значение 6 -> удалить 6 -> текстовое поле пусто -> оставить текстовое поле? Тогда это уже спасло бы 6

LoVo 07.08.2024 16:16

@johnsmith Я отредактировал свой ответ, установив значение 0, когда текстовое поле очищается.

LoVo 07.08.2024 16:27

Другие вопросы по теме