Как добавить наблюдаемое свойство при изменении других свойств - PullRequest
0 голосов
/ 15 февраля 2020

У меня есть следующий объект модели, который я использую, чтобы заполнить List Toggle для каждой строки, которая связана с measurement.isSelected

final class Model: ObservableObject {

    struct Measurement: Identifiable {
        var id = UUID()
        let name: String
        var isSelected: Binding<Bool>

        var selected: Bool = false

        init(name: String) {
            self.name = name

            let selected = CurrentValueSubject<Bool, Never>(false)
            self.isSelected = Binding<Bool>(get: { selected.value }, set: { selected.value = $0 })
        }
    }

    @Published var measurements: [Measurement]
    @Published var hasSelection: Bool = false  // How to set this?

    init(measurements: [Measurement]) {
        self.measurements = measurements
    }
}

Я бы хотел hasSelection свойство имеет значение true, если любое значение measurement.isSelected равно true. Я предполагаю, что Model нужно наблюдать за изменениями в measurements и затем обновлять его свойство hasSelection ... но я не знаю, с чего начать!

Идея в том, что hasSelection будет привязан к Button, чтобы включить или отключить его.


Model используется следующим образом ...

struct MeasurementsView: View {

    @ObservedObject var model: Model

    var body: some View {
        NavigationView {
            List(model.measurements) { measurement in
                MeasurementView(measurement: measurement)
            }
            .navigationBarTitle("Select Measurements")
            .navigationBarItems(trailing: NavigationLink(destination: NextView(), isActive: $model.hasSelection, label: {
                Text("Next")
            }))
        }
    }
}

struct MeasurementView: View {
    let measurement: Model.Measurement
    var body: some View {
        HStack {
            Text(measurement.name)
                .font(.subheadline)
            Spacer()
            Toggle(measurement.name, isOn: measurement.isSelected)
                .labelsHidden()
        }
    }
}

Для информации, вот скриншот того, что я пытаюсь добиться. Список выбираемых элементов со ссылкой для навигации, которая активируется при выборе одного или нескольких элементов и отключается при отсутствии выбранных элементов.

enter image description here

Ответы [ 2 ]

1 голос
/ 16 февраля 2020

@ user3441734 hasSelection в идеале должно быть свойством get only, которое имеет значение true, если любое из измерений. IsSelected - true

struct Data {
    var bool: Bool
}
class Model: ObservableObject {
    @Published var arr: [Data] = []
    var anyTrue: Bool {
        arr.map{$0.bool}.contains(true)
    }
}

пример (как и раньше) copy - paste - run

import SwiftUI

struct Data: Identifiable {
    let id = UUID()
    var name: String
    var on_off: Bool
}

class Model: ObservableObject {
    @Published var data = [Data(name: "alfa", on_off: false), Data(name: "beta", on_off: false), Data(name: "gama", on_off: false)]
    var bool: Bool {
        data.map {$0.on_off} .contains(true)
    }
}



struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack {
        List(0 ..< model.data.count) { idx in
            HStack {
                Text(verbatim: self.model.data[idx].name)
                Toggle(isOn: self.$model.data[idx].on_off) {
                    EmptyView()
                }
            }
        }
            Text("\(model.bool.description)").font(.largeTitle).padding()
        }
    }
}

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

При обновлении model.data

@Published var data ....

его издатель вызывает objectWillChange на ObservableObject.

Далее SwiftUI распознает, что ObservedObject необходимо Вид должен быть «обновлен». Представление воссоздается, и это заставит model.bool.description иметь значение fre sh.

LAST UPDATE

изменить эту часть кода

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        NavigationView {
        List(0 ..< model.data.count) { idx in
            HStack {
                Text(verbatim: self.model.data[idx].name)
                Toggle(isOn: self.$model.data[idx].on_off) {
                    EmptyView()
                }
            }
            }.navigationBarTitle("List")
            .navigationBarItems(trailing:

                NavigationLink(destination: Text("next"), label: {
                    Text("Next")
                }).disabled(!model.bool)

            )
        }
    }
}

и это именно то, что у вас есть в обновленном вопросе enter image description here

Попробуйте на реальном устройстве, в противном случае NavigationLink можно использовать только один раз (это Хорошо известная ошибка симулятора в текущем Xcode 11.3.1 (11C504)).

1 голос
/ 15 февраля 2020

Проблема с вашим кодом на данный момент заключается в том, что даже если вы наблюдаете изменения в measurements, они не будут обновляться при обновлении выбора, потому что вы объявили var isSelected: Binding<Bool> как привязку. Это означает, что SwiftUI хранит его вне вашей структуры, а сама структура не обновляется (остается неизменной).

Вместо этого вы можете вместо этого объявить @Published var selectedMeasurementId: UUID? = nil в вашей модели, поэтому ваш код будет чем-то как это:


import SwiftUI
import Combine

struct NextView: View {
    var body: some View {
        Text("Next View")

    }
}

struct MeasurementsView: View {

    @ObservedObject var model: Model

    var body: some View {

        let hasSelection = Binding<Bool> (
            get: {
                self.model.selectedMeasurementId != nil
            },
            set: { value in
                self.model.selectedMeasurementId = nil
            }
        )

        return NavigationView {
            List(model.measurements) { measurement in
                MeasurementView(measurement: measurement, selectedMeasurementId: self.$model.selectedMeasurementId)
            }
            .navigationBarTitle("Select Measurements")
            .navigationBarItems(trailing: NavigationLink(destination: NextView(), isActive: hasSelection, label: {
                Text("Next")
            }))
        }
    }
}

struct MeasurementView: View {


    let measurement: Model.Measurement
    @Binding var selectedMeasurementId: UUID?

    var body: some View {

        let isSelected = Binding<Bool>(
            get: {
                self.selectedMeasurementId == self.measurement.id
            },
            set: { value in
                if value {
                    self.selectedMeasurementId = self.measurement.id
                } else {
                    self.selectedMeasurementId = nil
                }
            }
        )

        return HStack {
            Text(measurement.name)
                .font(.subheadline)
            Spacer()
            Toggle(measurement.name, isOn: isSelected)
                .labelsHidden()
        }
    }
}

final class Model: ObservableObject {

    @Published var selectedMeasurementId: UUID? = nil

    struct Measurement: Identifiable {
        var id = UUID()
        let name: String

        init(name: String) {
            self.name = name
        }
    }

    @Published var measurements: [Measurement]


    init(measurements: [Measurement]) {
        self.measurements = measurements
    }
}

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

Если вы хотите поддерживать множественный выбор, вы можете вместо этого использовать набор выбранных идентификаторов.

Кроме того, похоже на iOS Симулятор имеет некоторые проблемы с навигацией, но я тестировал на физическом устройстве, и он работал.

...