Dakota Long
Dakota Long

Reputation: 55

How to struct's value such that all views can access its values in Swift/SwiftUI?

Currently I am decoding a JSON response from an API and storing it into a struct "IPGeolocation". I want to be able to store this data in a variable or return an instance of this struct such that I can access the values in views.

Struct:

struct IPGeolocation: Decodable {
    var location: Coordinates
    var date: String
    var sunrise: String
    var sunset: String
    var moonrise: String
    var moonset: String
}

struct Coordinates: Decodable{
    var latitude: Double
    var longitude: Double
}

URL extension with function getResult:

extension URL {
    func getResult<T: Decodable>(completion: @escaping (Result<T, Error>) -> Void) {
        URLSession.shared.dataTask(with: self) { data, response, error in
            guard let data = data, error == nil else {
                completion(.failure(error!))
                return
            }
            do {
                completion(.success(try data.decodedObject()))
            } catch {
                completion(.failure(error))
            }
        }.resume()
    }
}

Function that retrieves and decodes the data:

func getMoonTimes(lat: Double, long: Double) -> Void{
    urlComponents.queryItems = queryItems
    
    let url = urlComponents.url!
    
    url.getResult { (result: Result<IPGeolocation, Error>) in
        switch result {
        case let .success(result):
            print("Printing returned results")
            print(result)
        case let .failure(error):
            print(error)
        }
    }
}

My goal is to take the decoded information and assign it to my struct to be used in views after. The results variable is already an IPGeolocation struct once the function runs. My question lies in the best way to store it or even return it if necessary.

Would it make sense to have getResult return an IPGeolocation? Are there better/different ways?

Thanks!

EDIT: I made changes thanks to help from below comments from Leo Dabus.

func getMoonTimes(completion: @escaping (IPGeolocation?,Error?) -> Void) {
        print("STARTING FUNC")
        let locationViewModel = LocationViewModel()
        let apiKey = "AKEY"
        let latitudeString:String = String(locationViewModel.userLatitude)
        let longitudeString:String = String(locationViewModel.userLongitude)
        var urlComponents = URLComponents(string: "https://api.ipgeolocation.io/astronomy?")!
        let queryItems = [URLQueryItem(name: "apiKey", value: apiKey),
                          URLQueryItem(name: "lat", value: latitudeString),
                          URLQueryItem(name: "long", value: longitudeString)]
    urlComponents.queryItems = queryItems
    urlComponents.url?.getResult { (result: Result<IPGeolocation, Error>) in
        switch result {
        case let .success(geolocation):
            completion(geolocation, nil)
        case let .failure(error):
            completion(nil, error)
        }
    }
}

To call this method from my view:

struct MoonPhaseView: View {
    getMoonTimes(){geolocation, error in
        guard let geolocation = geolocation else {
            print("error:", error ?? "nil")
            return
        }
    }
...
...
...

Upvotes: 1

Views: 78

Answers (1)

Leo Dabus
Leo Dabus

Reputation: 236538

IMO it would be better to return result and deal with the error. Note that you are assigning the same name result which you shouldn't. Change it to case let .success(geolocation). What you need is to add a completion handler to your method because the request runs asynchronously and return an option coordinate and an optional error as well:

Note that I am not sure if you want to get only the location (coordinates) or the whole structure with your method. But the main point is to add a completion handler to your method and pass the property that you want or the whole structure.

func getMoonTimes(for queryItems: [URLQueryItem], completion: @escaping (Coordinates?,Error?) -> Void) {
    urlComponents.queryItems = queryItems
    urlComponents.url?.getResult { (result: Result<IPGeolocation, Error>) in
        switch result {
        case let .success(geolocation):
            completion(geolocation.location, nil)
        case let .failure(error):
            completion(nil, error)
        }
    }
}

Usage:

getMoonTimes(for: queryItems) { location, error in
    guard let location = location else {
        print("error:", error ?? "nil")
        return
    }
    print("Location:", location)
    print("Latitude:", location.latitude)
    print("Longitude:", location.longitude)
}

Upvotes: 1

Related Questions