Двусторонняя привязка в Swift Combine

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

class ViewModel: ObservableObject {
    @Published var progressBarValue: Double {
        didSet {
            textFieldValue = String(progressBarValue)
        }
    }
    @Published var textFieldValue: String {
        didSet {
            progressBarValue = Double(progressBarValue)
        }
    }
}

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

Есть ли способ обойти это с помощью Combine или простого быстрого кода?


person ldiqual    schedule 12.11.2019    source источник
comment
Apple (в своих выступлениях и документах на основе комбинаций / быстрых сообщений) всегда подчеркивает преимущества единого источника правды. В вашем случае это будет одиночный value, который оба элемента управления наблюдают и обновляют (через привязки), никогда не взаимодействуя друг с другом.   -  person Alladinian    schedule 14.11.2019


Ответы (2)


Продолжая мой комментарий, вот минимальный пример ползунка и текстового поля, которые контролируют (и контролируют) значение через двусторонние привязки:

class ViewModel: ObservableObject {
    @Published var progress: Double = 0
}

struct ContentView: View {
    @EnvironmentObject var model: ViewModel

    var body: some View {
        VStack {
            TextField("", value: self.$model.progress, formatter: NumberFormatter())
            Slider(value: self.$model.progress, in: 0...100)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(ViewModel())
    }
}

Обратите внимание, что мне также пришлось внедрить экземпляр ViewModel в мою среду в AppDelegate, чтобы это работало (как в предварительном просмотре, так и в реальном приложении)

person Alladinian    schedule 14.11.2019
comment
Это очень элегантный способ решить эту проблему, большое вам спасибо! - person ldiqual; 15.11.2019
comment
@JAHelia Подумайте об этом так: вместо того, чтобы устанавливать значение каждого элемента управления, я просто установлю значение a и позволю им обновляться при его изменении ... Никаких вызовов от одного к другому для синхронизации. .. - person Alladinian; 27.01.2020

Может быть, дополнительные проверки во избежание зацикливания сработают?

@Published var progressBarValue: Double {
    didSet {
        let newText = String(progressBarValue)

        if newText != textFieldValue {
            textFieldValue = newText
        }
    }
}
@Published var textFieldValue: String {
    didSet {
        if let newProgress = Double(textFieldValue),
            abs(newProgress - progressBarValue) > Double.ulpOfOne {
            progressBarValue = newProgress
        }
    }
}
person Sergey Kuryanov    schedule 14.11.2019