I have an iOS widget in which I would like to update the Widget View after I retrieve data from Firestore. I have looked all over the internet, including at this question. I am unable to update the view after the data is retrieved. Please look at the following code for the widget's view. The data is retrieved, but I am unable to update the UI element after I retrieve the data as I would like.
struct Runner_WidgetEntryView: View {
@State var text = ""
@State var textLoaded = false
var entry: Provider.Entry
var body: some View {
ZStack {
Image("back").resizable().scaledToFit()
Rectangle().foregroundColor(.black).cornerRadius(10).padding([.leading, .trailing], 10).padding([.top, .bottom], 15).overlay(Text(textLoaded ? text : "Loading..."))
}.onAppear {
let dispatchGroup = DispatchGroup()
FirebaseApp.configure()
let db = Firestore.firestore()
dispatchGroup.enter()
db.collection("collection").getDocuments { (snapshot, error) in
if error != nil {
text = "There was an error retrieving the text."
dispatchGroup.leave()
}
if let snapshot = snapshot {
for document in snapshot.documents {
if document.documentID == "Document ID" {
text = document.data()["qoute_name"] as! String
dispatchGroup.leave()
}
}
}
else {
text = "There was an error retrieving the text."
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
textLoaded = true
}
}
}
}
How do I update the TextView after I retrieve the data? Thanks in advance.
CodePudding user response:
SwiftUI needs to have its variables updated on the main thread.
So if you are not seeing the update, it could be due to the different threads involved.
Try this to update text
on the main thread.
EDIT-1:
.onAppear {
var fireTxt = "" // <-- here
let dispatchGroup = DispatchGroup()
FirebaseApp.configure()
let db = Firestore.firestore()
dispatchGroup.enter()
db.collection("collection").getDocuments { (snapshot, error) in
if error != nil {
fireTxt = "There was an error retrieving the text." // <-- here
}
if let snapshot = snapshot {
fireTxt = "No document found" // <-- here default text
for document in snapshot.documents {
if document.documentID == "Document ID" {
fireTxt = document.data()["qoute_name"] as! String // <-- here
}
}
}
else {
fireTxt = "There was an error retrieving the text." // <-- here
}
dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
textLoaded = true
text = fireTxt // <-- here on the main thread
}
}
Note, it is not a good idea to do all this db fetching in the .onAppear
. You should use a view model for that.
CodePudding user response:
After building off of @workingdog's answer and a lot of testing, I have come up with this solution:
The SwiftUI state cannot be updated from a closure, so it cannot be updated from within dispatchGroup.notify
or any other closure.
The workaround was to retrieve the data from Firebase when creating a timeline, and then saving the retrieved data to UserDefaults
. From there, the data can be read from UserDefaults
in the onAppear
method in the SwiftUI view.