>我使用组合连接到REST API,该API将一些数据提取到ObservableObject中。整个设置实际上只是MVVM。然后,在视图中观察可观察对象。现在,我遇到了一个似乎无法解决的错误。似乎发生的事情是视图被绘制了两次。它第一次使用 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)
}
)
})
}
}
打印语句的输出为 做 沉没成功 两者都只能在调试输出中找到一次。
这是视图:
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,表示视图已重新呈现。
对我来说有点奇怪的是,我本来希望在提取数据之前至少呈现一次视图,但我没有看到任何发生这种情况的迹象。
我已经尝试这个词两天了。任何帮助和建议将不胜感激!
我的猜测是,在你的视图层次结构中,你有一些东西导致ItemViewModel
刷新。 由于看起来您正在使用Firebase,因此我怀疑它是Firebase堆栈中的其他内容(例如用户身份验证状态?
我取出了您的 API 请求并将其替换为一个简单的Future
,该保证响应只是为了证明您的其余代码有效(确实如此)。
您可能的解决方案是:
- 按照其他回答者的建议使用
@StateObject
- 将 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()
}
}
}
我不知道为什么视图会重新绘制自己,但是您可以使用@StateObject
而不是@ObservedObject
来ItemViewModel
。拉取的数据应保持这种状态。
@StateObject var data = ItemViewModel()
每次丢弃并重绘视图时,都会重新创建ObservedObject
,StateObject
保留视图。