Данные из ObservedObject не отображаются в представлении SwiftUI

Я использую комбинацию для подключения к REST API, который извлекает некоторые данные в ObservableObject. Вся установка на самом деле просто MVVM. Затем ObservableObject наблюдается в представлении. Теперь я застрял с ошибкой, которую не могу решить. Кажется, что View отрисовывается дважды. В первый раз он обновляется значениями из ObservedObject. Затем он немедленно перерисовывается, но теперь извлеченные данные внезапно исчезают. Я подтвердил, что код, извлекающий данные, не выполняется во второй раз. Кроме того, средство просмотра иерархии представлений, похоже, предлагает, чтобы текстовые представления, которые должны отображать данные в представлении, каким-то образом удаляются при втором прогоне визуализации.

Вот как выглядит модель (код аутентификации взят из Firebase):

class ItemViewModel: ObservableObject {
    
    @Published var items = [ScheduleItem]()
    
    private var publisher: AnyCancellable?
    
    func fetchData() {
        
        let currentUser = Auth.auth().currentUser
        currentUser?.getIDTokenForcingRefresh(false, completion: { idToken, error in
        
            let url = URL(string: "abc")!
            var request = URLRequest(url: url)
            request.httpMethod = "GET"
            request.setValue("Bearer "+String(idToken!), forHTTPHeaderField: "Authorization")
     
            self.publisher = URLSession.shared
                .dataTaskPublisher(for: url)
                .map(\.data)
                .decode(
                    type: [ScheduleItem].self,
                    decoder: JSONDecoder()
                )
                .receive(on: DispatchQueue.main)
                .sink(
                    receiveCompletion: { completion in
                        switch completion {
                        case .failure(let error):
                            print("SINKERROR")
                            print(error)
                        case .finished:
                            print("SINKSUCCESS")
                        }
                    },
                    receiveValue: { repo in
                        print("DONE")
                        self.items.append(contentsOf: repo)
                    }
                )
        })
    }
}

Операторы печати выводят DONE SINKSUCCESS Оба они могут быть найдены только один раз в выводе отладки.

Это мнение:

struct TreatmentCardView: View {
    
    let startDate: String
    let uid: Int
    
    @ObservedObject var data = ItemViewModel()
    
    @State private var lastTime: String = "2"
    
    var body: some View {
        ZStack {
            GeometryReader { geometry in
                VStack{
                    
                    Text("Headline")
                        .font(.title)
                        .foregroundColor(Color.white)
                        .padding([.top, .leading, .bottom])
                    
                    Print("ISARRAYEMPTY")
                    Print(String(data.items.isEmpty))
                    
                    Text(String(data.items.isEmpty))
                                .font(.headline)
                                .fontWeight(.light)
                                .foregroundColor(Color.white)
                                .frame(width: 300, height: 25, alignment: .leading)
                                .multilineTextAlignment(.leading)
                                .padding(.top)
                      
                     
                }
            }
        }
        .background(Color("ColorPrimary"))
        .cornerRadius(15)
        .onAppear{
            self.data.fetchData()
        }
    }
}

Print (String (data.items.isEmpty)) сначала имеет значение false, затем true, что указывает на повторную визуализацию представления.

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

Я уже два дня пытаюсь сказать это слово. Любая помощь и совет приветствуются!


person DrGonzoX    schedule 27.01.2021    source источник


Ответы (2)


Я не знаю, почему представление перерисовывается, но вы можете использовать @StateObject вместо @ObservedObject для своего ItemViewModel. Ваши извлеченные данные должны оставаться такими.

@StateObject var data = ItemViewModel()

ObservedObject будет воссоздаваться каждый раз, когда представление отбрасывается и перерисовывается, где StateObject сохраняет его.

person Mofawaw    schedule 27.01.2021

Я предполагаю, что выше в иерархии представлений у вас есть что-то, что заставляет ItemViewModel обновляться. Поскольку похоже, что вы используете Firebase, я подозреваю, что это что-то еще в вашем стеке Firebase (например, статус авторизации пользователя?).

Я взял ваш запрос API и заменил его простым Future, который гарантированно ответит только для того, чтобы доказать, что остальная часть вашего кода работает (что так и есть).

Ваши возможные решения:

  1. Используйте @StateObject, как предложил другой ответчик
  2. Переместите вызовы API в ObservableObject, который хранится выше в иерархии представлений (например, и EnvironmentObject), который передается в TreatmentCardView вместо того, чтобы каждый раз создавать его там.

Чтобы увидеть, получаете ли вы просто еще один побочный эффект от вызова API Firebase, вот моя упрощенная версия без Firebase:

class ItemViewModel: ObservableObject {
    
    @Published var items = [String]()
    
    private var publisher: AnyCancellable?
    
    func fetchData() {

        let myFuture = Future<String, Error> { promise in
            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                promise(.success("TEST STRING"))
            }
        }
        
        self.publisher = myFuture
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { completion in
                    switch completion {
                    case .failure(let error):
                        print("SINKERROR")
                        print(error)
                    case .finished:
                        print("SINKSUCCESS")
                    }
                },
                receiveValue: { repo in
                    print("DONE")
                    self.items.append("String")
                }
            )
    }
}

struct TreatmentCardView: View {
    
    let startDate: String
    let uid: Int
    
    @ObservedObject var data = ItemViewModel()
    
    @State private var lastTime: String = "2"
    
    var body: some View {
        ZStack {
            GeometryReader { geometry in
                VStack{
                    
                    Text("Headline")
                        .font(.title)
                        .foregroundColor(Color.white)
                        .padding([.top, .leading, .bottom])
//
//                    Print("ISARRAYEMPTY")
//                    Print(String(data.items.isEmpty))
                    
                    Text(String(data.items.isEmpty))
                                .font(.headline)
                                .fontWeight(.light)
                                .foregroundColor(Color.white)
                                .frame(width: 300, height: 25, alignment: .leading)
                                .multilineTextAlignment(.leading)
                                .padding(.top)
                      
                     
                }
            }
        }
        .background(Color("ColorPrimary"))
        .cornerRadius(15)
        .onAppear{
            self.data.fetchData()
        }
    }
}
person jnpdx    schedule 27.01.2021