SwiftU Я получаю значения другой ViewModel в ViewModel - PullRequest
1 голос
/ 07 мая 2020

Я вижу эти фильтры, и все они обновляют FilterViewModel, который затем занимается фильтрацией данных. Одно из этих представлений, SearchAddressView, ожидает PlacemarkViewModel, а не FilterViewModel, потому что оно предоставляет раскрывающийся список адресов, когда пользователь начинает печатать. Там много кода, поэтому я не хочу дублировать этот код в свой FilterViewModel

Однако мне нужно прочитать @Published var placemark: Placemark от PlacemarkViewModel до FilterViewModel. Я пытаюсь импортировать PlacemarkViewModel в FilterViewModel, а затем использовать didSet { }, чтобы прочитать его значения, но он не работает.

Итак, идея в том, что ... пока пользователи ищут адрес, это обновляет PlacemarkViewModel, но FilterViewModel также необходимо получить это значение. Есть идеи, как этого добиться?

struct FiltersView: View {
    @ObservedObject var filterViewModel: FilterViewModel

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                FilterButtonView(title: LocalizedStringKey(stringLiteral: "category"), systemName: "square.grid.2x2.fill") {
                    CategoryFilterView(filterViewModel: self.filterViewModel)
                }

                FilterButtonView(title: LocalizedStringKey(stringLiteral: "location"), systemName: "location.fill") {
                    SearchAddressView(placemarkViewModel: self.filterViewModel.placemarkViewModel)
                }

                FilterButtonView(title: LocalizedStringKey(stringLiteral: "sort"), systemName: "arrow.up.arrow.down") {
                    SortFilterView(filterViewModel: self.filterViewModel)
                }
            }
        }
    }
}

FilterViewModel

class FilterViewModel: ObservableObject, LoadProtocol {
    @Published var placemarkViewModel: PlacemarkViewModel() {
           didSet {
            print("ok") // nothing
           }
       }
}

PlacemarkViewModel

class PlacemarkViewModel: ObservableObject {
    let localSearchCompleterService = LocalSearchCompleterService()
    let locationManagerService = LocationManagerService()
    @Published var addresses: [String] = []

    // I need this value in my FilterViewMode;
    @Published var placemark: Placemark? = nil
    @Published var query = "" {
        didSet {
            localSearchCompleterService.autocomplete(queryFragment: query) { (addresses: [String]) in
                self.addresses = addresses
            }
        }
    }

    init(placemark: Placemark? = nil) {
        self.placemark = placemark
    }

    var address: String {
        if let placemark = placemark {
            return "\(placemark.postalCode) \(placemark.locality), \(placemark.country)"
        }

        return ""
    }

    func setPlacemark(address: String) {
        locationManagerService.getLocationFromAddress(addressString: address) { (coordinate, error) in
            let location: CLLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
            self.locationManagerService.getAddressFromLocation(location: location) { (placemark: CLPlacemark?) in
                if let placemark = placemark {
                    self.placemark = Placemark(placemark: placemark)
                    self.query = placemark.name ?? ""
                }
            }
        }
    }

    func getAddressFromLocation() {
        locationManagerService.getLocation { (location: CLLocation) in
            self.locationManagerService.getAddressFromLocation(location: location) { (placemark: CLPlacemark?) in
                if let placemark = placemark {
                    self.placemark = Placemark(placemark: placemark)
                    self.query = placemark.name ?? ""
                }
            }
        }
    }
}

Ответы [ 2 ]

1 голос
/ 09 мая 2020

В принятом ответе много запаха кода, и я считаю необходимым его прояснить, прежде чем это загрязнение распространится на разработку SwiftUI.

  • Модель вложенных представлений никоим образом не должна поощряться.

Модель представления неоднозначна (почему в ней разрешены логические c / побочные эффекты?)

Модель вложенного представления? Что это хотя бы значит? И снова ничто не мешает вам спрятать в нем побочные эффекты, которые сложнее отследить и отладить. цикл, выпуск).

например; init(placemarkViewModel: PlacemarkViewModel) { self.placemarkViewModel = placemarkViewModel }

Аргумент, который я видел относительно модели вложенного представления, состоит в том, что «это обычная практика».

Нет, это частая ошибка. Что вы чувствуете, когда кто-то это пишет?

`vm1.vm2.vm3.modelY.property1.vm.p2`

Потому что это именно то, что произойдет, если вы будете поощрять это.

  • сетевой вызов с побочным эффектом в init ()

MVVM часто обрабатывает ViewModel похож на безобидный тип значения, когда на самом деле это ссылочный тип, заполненный побочными эффектами Control / business logi c /.

Это один из таких примеров. Когда вы создаете «модель», вы инициируете сетевой запрос с побочным эффектом. Это повредит неосведомленному разработчику, который использует вашу «модель».

  • Разделение сети и использование типа значения

Сеть не должна быть единственной причиной, по которой вы создаете модель эталонного типа. У вас может быть выделенный объект сетевой службы и модель типа значения.

Если вы уберете всю сеть из «ViewModel», а оставшаяся «ViewModel» сочтете тривиальной или глупой, то вы на правильном пути.

Вместо двух моделей представления и неявных зависимостей вы должны использовать @EnvironmentObject.

например;

final class SharedState: ObservableObject {
    @Published var placemark: Placemark?
    // other stuff you want to publish

    func updatePlacemark() {
        // network call to update placemark and trigger view update
    }

}
let state = SharedState()
state.updatePlacemark() // explicit call for networking with side effects
// set as environmentObject, e.g.; ContentView().environmentObject(state)

Ваш SearchAddressView может удалить внешний параметр и напрямую получить доступ к environmentObject.

Таким образом, вы можете удалить все проходящие модели представления:

FilterButtonView(title: LocalizedStringKey(stringLiteral: "location"), systemName: "location.fill") {
                SearchAddressView()
}

Подождите, но это делает бесполезными мои причудливые модели просмотра?

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

1 голос
/ 08 мая 2020

Здесь возможен подход

class FilterViewModel: ObservableObject, LoadProtocol {
    @Published var placemarkViewModel = PlacemarkViewModel()

    private var cancellable: AnyCancellable? = nil
    init() {
        cancellable = placemarkViewModel.$placemark
            .sink { placemark in
                // handle updated placemark here
            }
    }
}
...