Hi I'm a beginner at swift and I'm using a ViewModel and I'm trying to fetch data from an api I've setup locally as follows;
//
// LocationsViewModel.swift
//
//
import Foundation
import MapKit
import SwiftUI
class LocationsViewModel: ObservableObject {
// all loaded locations
@Published var locations: [Location]
// Current location on map
@Published var mapLocation: Location {
didSet {
updateMapRegion(location: mapLocation)
}
}
// current region on map
@Published var mapRegion: MKCoordinateRegion = MKCoordinateRegion()
// show list of locations
@Published var showLocationsList: Bool = false
// show location detail via sheet
@Published var sheetLocation: Location? = nil
let mapSpan = MKCoordinateSpan(
latitudeDelta: 0.1,
longitudeDelta: 0.1
)
init() {
LocationsDataService.fetch()
let locations = LocationsDataService.locations
self.locations = locations
self.mapLocation = locations.first!
self.updateMapRegion(location: locations.first!)
}
private func updateMapRegion(location: Location) {
withAnimation(.easeInOut) {
mapRegion = MKCoordinateRegion(
center: location.coordinates,
span: mapSpan
)
}
}
func toggleLocationsList() {
withAnimation(.spring()) {
showLocationsList.toggle()
}
}
func showNextLocation(location: Location) {
withAnimation(.easeInOut) {
mapLocation = location
showLocationsList = false
}
}
}
However this is throwing me an error on the following two lines;
LocationsDataService.fetch()
let locations = LocationsDataService.locations
Which are;
Instance member 'fetch' cannot be used on type 'LocationsDataService'; did you mean to use a value of this type instead?
Instance member 'locations' cannot be used on type 'LocationsDataService'; did you mean to use a value of this type instead?
My data service is as follows;
//
// LocationsDataService.swift
//
import Foundation
import MapKit
class LocationsDataService {
let token = "2|asOnUG27uCrcVuGxOO65kS25mX0nUSls5ApfemQy";
@Published var locations: [Location] = []
func fetch() {
guard let url = URL(string: "http://localhost:8080/api/locations") else {
print("Invalid url...")
return
}
var urlRequest = URLRequest( //2
url: url
)
urlRequest.setValue( //3
"Bearer \(token)",
forHTTPHeaderField: "Authentication"
)
urlRequest.setValue( //4
"application/json;charset=utf-8",
forHTTPHeaderField: "Content-Type"
)
let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] data, _, error in
guard let data = data, error == nil else {
return
}
do {
let apiLocations = try JSONDecoder().decode([Location].self, from: data)
DispatchQueue.main.async {
self?.locations = apiLocations
}
} catch {
print(error)
}
}
task.resume()
}
}
This is my location model;
//
// Location.swift
//
//
import Foundation
import MapKit
import CoreLocation
struct Location: Identifiable, Equatable, Codable {
static func == (lhs: Location, rhs: Location) -> Bool {
return lhs.id == rhs.id
}
let name: String
let cityName: String
// let coordinates: CLLocationCoordinate2D
let description: String
let imageNames: [String]
let link: String
var id: String {
// create computed variable for id
name cityName
}
let latitude: Double
let longitude: Double
var coordinates: CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: CLLocationDegrees(latitude), longitude: CLLocationDegrees(longitude))
}
}
Any ideas how I can fetch the data from the api in my service class and use it in the LocationsViewModel??
CodePudding user response:
By calling LocationsDataService.fetch()
you are calling static function fetch which doesn't exist. You need an instance of LocationsDataService
, so basically you need to instantiate it and then call fetch()
like this
let dataService = LocationsDataService()
dataService.fetch()
It is the same story with locations
property.
Otherwise you can make fetch
and locations
static on LocationsDataService
but usually that is something you don't want.
CodePudding user response:
There are several little mistakes in your code. The First one is that you are accessing fetch()
and locations
as if they are are static
variables. They are not, you need an instance of the service, something like
let service = LocationsDataService()
That would be put at class
level. But this won't work as you expect...
fetch()
is asynchronous so locations
will not have a value immediately. Because of your usage I suggest you use a "completion handler" and make your service a struct
instead of a class
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
That being said, this is why...
An @Published
only works inside of an ObservableObject
it can't just be a class
.
You can't chain ObservableObject
s and expect to get an update by itself. You would need to subscribe using .sink
in order to get updates in the view model.
But you aren't getting several updates so you don't need to add the subscription a completion handler would give you the one time result.