skip to Main Content

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


  1. 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:

    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
        }
        
        do {
            let decodedBus = try JSONDecoder().decode([BusAPI].self, from: data)
            DispatchQueue.main.async {
                self?.bus = decodedBus
            }
        } catch {
            print(error)
        }
    }
    
    task.resume()  }
    

    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.

    Login or Signup to reply.
  2. 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 when
    you decide to do so.

    One simple way to do this, is to add a .refreshable modifier to your List, so that when
    the user do a
    pull-to-refresh gesture, the list contents is re-fetched and thus updated with the latest bus info.

     List {...}
       .refreshable {
          viewModel.fetch()
       }
    

    Note, you are using an insecure http connection, Apple requires to use https.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search