【问题标题】:Data from ObservedObject not rendered in SwiftUI view来自 ObservedObject 的数据未在 SwiftUI 视图中呈现
【发布时间】:2021-05-01 15:15:53
【问题描述】:

我使用 combine 连接到 REST API,该 API 将一些数据拉入 ObservableObject。整个设置实际上只是 MVVM。 ObservableObject 然后在视图中被观察。现在,我遇到了一个似乎无法解决的错误。似乎发生的是视图被绘制了两次。第一次使用 ObservedObject 中的值进行更新。然后它立即重新绘制,但现在拉取的数据突然消失了。我已经确认不会再次执行提取数据的代码。此外,View Hierarchy Viewer 似乎暗示应该在视图中渲染数据的文本视图在第二次渲染运行时以某种方式被删除。

这是模型的样子(身份验证相关代码来自 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)
                    }
                )
        })
    }
}

打印语句的输出是 完毕 汇成功 两者都只能在调试输出中找到一次。

这是视图:

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)) 先为假,后为真,表示视图被重新渲染。

对我来说有点奇怪的是,在拉取数据之前,我希望视图至少呈现一次,但我没有看到任何迹象表明这种情况正在发生。

这两天我一直在努力创造这个词。非常感谢任何帮助和建议!

【问题讨论】:

    标签: swiftui combine observableobject observedobject


    【解决方案1】:

    我的猜测是,在您的视图层次结构中,您有一些导致 ItemViewModel 刷新的东西。由于看起来您正在使用 Firebase,我怀疑它是您的 Firebase 堆栈中的其他内容(例如用户身份验证状态?)。

    我取出您的 API 请求并用一个简单的 Future 替换它,保证响应只是为了证明您的其余代码有效(确实如此)。

    您可能的解决方案是:

    1. 使用@StateObject,正如另一位回答者所建议的那样
    2. 将您的 API 调用移动到存储在视图层次结构中较高位置的 ObservableObject(例如,EnvironmentObject),然后传递给 TreatmentCardView,而不是每次都在那里创建。

    要查看您是否只是从 Firebase API 调用中获得了另一个副作用,这是我没有 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()
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我不知道为什么视图会自行重绘,但您可以使用@StateObject 而不是@ObservedObject 作为ItemViewModel。您提取的数据应该保持这种状态。

      @StateObject var data = ItemViewModel()
      

      ObservedObject 将在每次视图被丢弃和重绘时重新创建,StateObject 保留它。

      【讨论】:

        猜你喜欢
        • 2021-02-17
        • 1970-01-01
        • 2020-12-21
        • 2020-02-09
        • 2020-07-12
        • 2020-10-22
        • 2020-05-02
        • 1970-01-01
        • 2019-01-06
        相关资源
        最近更新 更多