I have a List that is updated with a Fetch class, an ObservableObject. It has an init function. This is that Fetch class.
@Published private(set) var items: [ItemsResult] = []
init() {
let url = URL(string: "[redacted]")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let itemsData = data {
let decodedData = try JSONDecoder().decode([ItemsResult].self, from: itemsData)
DispatchQueue.global(qos: .utility).async {
DispatchQueue.main.async {
print("running task")
self.items = decodedData
}
}
print(self.items)
} else {
print("No data")
}
} catch {
print("Error: \(error)")
}
}.resume()
}
When the app is build, it correctly displays the data returned by the API and it matches the database. However when I tap / click on one in order to delete it, or use the textarea I've added to add a new item it doesn't update.
struct TickrApp: View {
@EnvironmentObject var fetch: Fetch
var body: some View {
NavigationView {
Form {
Section {
VStack(alignment: .center) {
Text("Welcome to Tickr")
}
}
Section {
List(fetch.items) { item in
CheckView(checked: item.done, title: item.content.replacingOccurrences(of:"_", with: " "))
}
}
AddItemView()
}.navigationBarTitle(Text("Tickr"))
}
}
}
The database is being updated as shown when I log the decodedData they respond, however in each I just call Fetch()
. Requests are made the same in all three cases.
One of the calls, for text input.
func toggle() {
checked = !checked
let url = URL(string: "")!
var req = URLRequest(url: url)
req.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: req) { data, response, error in
guard let _ = data,
let response = response as? HTTPURLResponse,
error == nil else {
print("error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else {
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
}
task.resume()
Fetch()
In order to update the list visually I need to completely quit the app / rerun it in order to have the new and/or deleted items show correctly. No errors show about background publishing changes or anything.
It appears that you're trying to call Fetch()
to refresh your data. There are two things that are going to be a problem with this:
You're calling it outside of the dataTask
completion handler. That means that it may get called before the write finishes
Calling Fetch()
just creates a new instance of Fetch
, when what you really want to do is update the results on your existing instance.
I'm assuming that your first code snipped is from Fetch
. I'd change it to look more like this:
class Fetch: ObservableObject {
@Published private(set) var items: [ItemsResult] = []
init() {
performFetch()
}
func performFetch() {
//your existing fetch code that was in `init`
}
}
Then, in your AddItemView
and CheckView
, make sure you have this line:
@EnvironmentObject var fetch: Fetch
This will ensure you're using the same instance of Fetch
so your list will reflect the same collection of results.
Once you're done with an operation like toggle()
, call self.fetch.performFetch()
to update your results. So, your last code snippet would turn into something like this:
let task = URLSession.shared.dataTask(with: req) { data, response, error in
//guard statements to check for errors
self.fetch.performFetch() //perform your refresh on the *existing* `Fetch` instance
}
A bigger refactor would involve moving your async code (like toggle
) to a view model, instead of doing any of it in a View
code. Also, look into using the URLSession
Publishers using Combine, since you're using SwiftUI: https://developer.apple.com/documentation/foundation/urlsession/processing_url_session_data_task_results_with_combine