SwiftUI Picker не привязывается к ObservedObject
Я пытаюсь заполнить сборщик данными, асинхронно полученными из внешнего API.
Это моя модель:
struct AppModel: Identifiable {
var id = UUID()
var appId: String
var appBundleId : String
var appName: String
var appSKU: String
}
Класс, который получает данные и публикует:
class AppViewModel: ObservableObject {
private var appStoreProvider: AppProvider? = AppProvider()
@Published private(set) var listOfApps: [AppModel] = []
@Published private(set) var loading = false
fileprivate func fetchAppList() {
self.loading = true
appStoreProvider?.dataProviderAppList { [weak self] (appList: [AppModel]) in
guard let self = self else {return}
DispatchQueue.main.async() {
self.listOfApps = appList
self.loading = false
}
}
}
init() {
fetchAppList()
}
}
Представление:
struct AppView: View {
@ObservedObject var appViewModel: AppViewModel = AppViewModel()
@State private var selectedApp = 0
var body: some View {
ActivityIndicatorView(isShowing: self.appViewModel.loading) {
VStack{
// The Picker doesn't bind with appViewModel
Picker(selection: self.$selectedApp, label: Text("")) {
ForEach(self.appViewModel.listOfApps){ app in
Text(app.appName).tag(app.appName)
}
}
// The List correctly binds with appViewModel
List {
ForEach(self.appViewModel.listOfApps){ app in
Text(app.appName.capitalized)
}
}
}
}
}
}
В то время как представление списка связывается с наблюдаемым объектом appViewModel, средство выбора ведет себя иначе. Я не понимаю почему. Любая помощь?
2 ответа
Я отправил отчет об ошибке, FB7670992. Вчера Apple ответила, предлагая подтвердить такое поведение в iOS 14, beta 1. Похоже, что теперь проблема решена.
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Picker("", selection: $viewModel.wheelPickerValue) {
ForEach(viewModel.objects) { object in
Text(object.string)
}
}
.pickerStyle(WheelPickerStyle())
.labelsHidden()
}
}
куда
struct Object: Identifiable {
let id = UUID().uuidString
let string: String
}
class ViewModel: ObservableObject {
private var counter = 0
@Published private(set) var objects: [Object] = []
@Published var segmentedPickerValue: String = ""
@Published var wheelPickerValue: String = ""
fileprivate func nextSetOfValues() {
let newCounter = counter + 3
objects = (counter..<newCounter).map { value in Object(string: "\(value)") }
let id = objects.first?.id ?? ""
segmentedPickerValue = id
wheelPickerValue = id
counter = newCounter
}
init() {
let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
guard let self = self else { timer.invalidate(); return }
self.nextSetOfValues()
}
timer.fire()
}
}
Результаты в:
Я не могу вставить это в ваш код, потому что он неполный, но вот образец. Сборщики не должны быть динамическими. Их нужно полностью перезагрузить.
class DynamicPickerViewModel: ObservableObject {
@Published private(set) var listOfApps: [YourModel] = []
@Published private(set) var loading = false
fileprivate func fetchAppList() {
loading = true
DispatchQueue.main.async() {
self.listOfApps.append(YourModel.addSample())
self.loading = false
}
}
init() {
fetchAppList()
}
}
struct DynamicPicker: View {
@ObservedObject var vm = DynamicPickerViewModel()
@State private var selectedApp = ""
var body: some View {
VStack{
//Use your loading var to reload the picker when it is done
if !vm.loading{
//Picker is not meant to be dynamic, it needs to be completly reloaded
Picker(selection: self.$selectedApp, label: Text("")) {
ForEach(self.vm.listOfApps){ app in
Text(app.name!).tag(app.name!)
}
}
}//else - needs a view while the list is being loaded/loading = true
List {
ForEach(self.vm.listOfApps){ app in
Text(app.name!.capitalized)
}
}
Button(action: {
self.vm.fetchAppList()
}, label: {Text("fetch")})
}
}
}
struct DynamicPicker_Previews: PreviewProvider {
static var previews: some View {
DynamicPicker()
}
}