Before I say anything else, I’m actually a beginner level in Swift and SwiftUI but I’m always trying to accomplish things that don’t meet my level of knowledge. I’ve had an idea of bringing bus information to live activities. I’ve managed to find an api of my local transportation and created a SwiftUI list view for the next bus in a specific bus station.
There’s actually a tutorial on youtube that shows how to fetch the data (https://www.youtube.com/watch?v=CimY_Sr3gWw&t=221) and he uses @Published var bus: [BusAPI] = [] to let the view know that when the API data changes then it has to refresh.
I’m fetching the API data this way.
import Foundation
import SwiftUI
struct BusAPI: Hashable, Codable {
let route_code: String
let veh_code: String
let btime2: String
}
class ViewModel: ObservableObject {
@Published var bus: [BusAPI] = []
func fetch() {
guard let url = URL(string: "http://telematics.oasa.gr/api/?act=getStopArrivals&p1=060475") else {
return
}
let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data, error == nil else {
return
}
//Convert to JSON
do {
let bus = try JSONDecoder().decode([BusAPI].self, from: data)
DispatchQueue.main.async {
self?.bus = bus
}
}
catch {
print(error)
}
}
task.resume()
}
}
The View looks like this:
//
// ContentView.swift
// BusDemoAPI
//
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
NavigationView {
List {
ForEach(viewModel.bus, id: .self) { bus in
HStack {
Text("N. Ελβετία - Άνω Πατήσια")
Spacer()
Text("(bus.btime2)'")
}.padding(3)
}
}.navigationTitle("Στάση ΟΠΑ")
.onAppear {
viewModel.fetch()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
My actual question is why doesnt the view refreshes when the data changes? It’s always stuck on the time that it first fetched the data.
I’ve tried to find a solution on google but nothing actually helped.
2
Answers
In your code, you’re correctly using @Published to indicate that the bus array is an observable property. This should trigger the refresh of the view whenever the data changes. However, there might be a small mistake in your code that is preventing the view from refreshing.
The issue seems to be related to the fact that you have two variables with the same name (bus) inside your fetch() method. One is the array of BusAPI objects in your view model, and the other is a local constant inside the do-catch block where you decode the JSON response.
To resolve this issue, you can give a different name to the local constant inside the do-catch block. Here’s the modified fetch() method:
By giving a different name (decodedBus in this case) to the local constant, you avoid shadowing the bus property of your view model, allowing the correct assignment of the fetched data to the observable property. This should trigger the view refresh as expected whenever new data is fetched.
Give this modification a try and see if it resolves the issue with the view not refreshing when the data changes.
The answer to your question
My actual question is why doesnt the view refreshes when the data changes?
is because your code does not
update by itself when the data on the server is updated. You need to fetch the
new
data yourself whenyou decide to do so.
One simple way to do this, is to add a
.refreshable
modifier to yourList
, so that whenthe user do a
pull-to-refresh gesture, the list contents is re-fetched and thus updated with the latest bus info.
Note, you are using an insecure
http
connection, Apple requires to usehttps
.