SwiftUI/Combine: подписаться на изменение значения @Binding

#swift #xcode #swiftui #combine

#swift #xcode #swiftui #объединить

Вопрос:

У меня есть представление с моделью представления, и действия в этом представлении могут изменить модель представления. Чтобы иметь возможность разбивать логику на части, которые можно использовать повторно, у меня есть часть представления как собственное представление с привязкой @ к значениям, которые ему нужны. Теперь я хочу иметь возможность выполнять некоторую логику, основанную на изменениях значений, не обязательно только просматривать изменения. Как я могу это сделать? Если бы это было обычное свойство, я бы внедрил didSet, но это ни к чему меня не привело. Я хотел использовать Combine to и рассматривать @Binding как издателя, но я также не смог найти способ сделать это. Предложения?

Вот код:

 class ViewModel: ObservableObject {
    @Published var counter: Int = 0
}

struct Greeter: View {
    @Binding var counter: Int {
        didSet {
            // this isn't printed....
            print("Did set -> (counter)")
        }
    }
    
    init(counter: Binding<Int>) {
        self._counter = counter
        
        // ...so how about setting up a subscription to the @Binding counter above here?
    }
    
    var body: some View {
        Text("Hello, world #(counter)!")
            .padding()
    }
}

struct ContentView: View {
    
    @ObservedObject var viewModel: ViewModel
    
    var body: some View {
        VStack {
            Greeter(counter: $viewModel.counter)
            Button("Go!") {
                viewModel.counter  = 1
            }
        }
    }
}
  

Итак, я хочу сохранить структуру, в которой данные находятся в ViewModel, и что только ее части передаются в подвиде. И это в подвиде (приветствие) Я хочу иметь возможность что-то сделать (скажем, напечатать значение, как в didSet)

Ответ №1:

Вот возможный подход. Протестировано с Xcode 11.4 / iOS 13.4 (SwiftUI 1.0 )

 struct Greeter: View {
    @Binding var counter: Int

    var body: some View {
        Text("Hello, world #(counter)!")
            .padding()
            .onReceive(Just(counter)) {    // << subscribe
                print(">> greeter: ($0)") // << use !!
            }
    }
}
  

Примечание для SwiftUI 2.0 : вы можете использовать .onChange(of: counter) { вместо этого (который на самом деле является встроенной заменой выше)

Комментарии:

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

2. мне потребовалась вечность, чтобы найти это для поддержки ios13, идеальная замена onChange, спасибо 🙂

3. Он просто вызывается при каждом обновлении представления, не так ли?

4. Да, это стек в цикле

5. Для меня .onReceive(Just(binding)) работает, но .onChange(of: binding) нет.