SwiftUI - получить координаты пользователя для передачи в вызове API

Эта проблема преследует меня уже несколько месяцев, и я считаю, что она связана с тем, что я использую неправильную структуру и процедуру.

Я пытаюсь сделать API-вызов API Yelp и передать переменные для широты/долготы пользователя. Я могу получить широту/долготу на основе моего текущего LocationManager, однако, когда кажется, что широта/долгота становится доступной только ПОСЛЕ вызова API, поэтому API получает значения по умолчанию 0,0 для обеих широта/долгота .

Я очень новичок, когда дело доходит до этого, но есть ли способ, которым я мог бы настроить экран загрузки, который захватывает широту / долготу в фоновом режиме, и к тому времени, когда мой ExploreView показывает, информация о реальном местоположении была установлена ?

Ниже мой LocationManager и ExploreView

LocationManager

import Foundation
import CoreLocation

class LocationManager: NSObject, ObservableObject {

private let locationManager = CLLocationManager()
let geoCoder = CLGeocoder()

@Published var location: CLLocation? = nil
@Published var placemark: CLPlacemark? = nil

override init() {
    super.init()
    self.locationManager.delegate = self
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
    self.locationManager.distanceFilter = kCLDistanceFilterNone
    self.locationManager.requestWhenInUseAuthorization()
    self.locationManager.startUpdatingLocation()
    
}

func geoCode(with location: CLLocation) {
    geoCoder.reverseGeocodeLocation(location) { (placemark, error) in
        if error != nil {
            print(error!.localizedDescription)
        } else {
            self.placemark = placemark?.first
        }
    }
}

func startUpdating() {
    self.locationManager.delegate = self
    self.locationManager.requestWhenInUseAuthorization()
    self.locationManager.startUpdatingLocation()
    }
}

extension LocationManager: CLLocationManagerDelegate {

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.first else {
        return
    }
    self.location = location
    self.geoCode(with: location)
    }
}

ExploreView (первое представление, которое отображается при запуске)

import SwiftUI
import CoreLocation
import Foundation


struct ExploreView: View {
    @ObservedObject  var location = LocationManager()
    @ObservedObject var fetcher: RestaurantFetcher


init() {
    let location = LocationManager()
    self.location = location
    self.fetcher = RestaurantFetcher(locationManager: location)
    self.location.startUpdating()
}

var body: some View {
        ScrollView (.vertical) {
            VStack {
                HStack {
                    Text("Discover ")
                        .font(.system(size: 28))
                        .fontWeight(.bold)
                  +  Text(" \(location.placemark?.locality ?? "")")
                        .font(.system(size: 28))
                        .fontWeight(.bold)
                    Spacer()                       
                }
                HStack {
                    SearchBar(text: .constant(""))
                }.padding(.top, 16)                
                HStack {
                    Text("Featured Restaurants")
                        .font(.system(size: 24))
                        .fontWeight(.bold)
                    Spacer()                       
                    NavigationLink(
                        destination: FeaturedView(),
                        label: {
                            Text("View All")
                        })                        
                }.padding(.vertical, 30)                 
                HStack {
                    Text("All Cuisines")
                        .font(.system(size: 24))
                        .fontWeight(.bold)
                    Spacer()
                }                
                Spacer()
            }.padding()
        }      
    }
}

public class RestaurantFetcher: ObservableObject {
    @Published var businesses = [RestaurantResponse]()
    @ObservedObject var locationManager: LocationManager
    let location = LocationManager()

var lat: String {
    return "\(location.location?.coordinate.latitude ?? 0.0)"
}

var long: String {
    return "\(location.location?.coordinate.longitude ?? 0.0)"
}

init(locationManager: LocationManager) {
    let location = LocationManager()
    self.locationManager = location
    self.location.startUpdating()
    
    load()
}

func load() {
    print("\(location.location?.coordinate.latitude ?? 0.0)")
    print("user latitude top of function")
    //Returns default values of 0.0
    let apikey = "APIKEY Here"
    let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(lat)&longitude=\(long)&radius=40000")!
    var request = URLRequest(url: url)
    request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
    request.httpMethod = "GET"
    
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        do {
            if let d = data {
                print("\(self.location.location?.coordinate.longitude ?? 0.0)")
                let decodedLists = try JSONDecoder().decode(BusinessesResponse.self, from: d)
               
                // Returns actual location coordinates
                DispatchQueue.main.async {
                    self.businesses = decodedLists.restaurants
                }
            } else {
                print("No Data")
            }
        } catch {
            print ("Caught")
        }
    }.resume()
    }
}

person Jason Tremain    schedule 26.08.2020    source источник
comment
Удалось ли вам решить ее каким-либо образом?   -  person Matthew Morek    schedule 18.11.2020


Ответы (1)


Попробуйте следующий измененный код (мне нужно было сделать несколько повторений, так что будьте внимательны - возможны опечатки).

Основная идея состоит в том, чтобы подписаться на обновленного издателя местоположения LocationManager, чтобы отслеживать явные изменения местоположения и выполнять следующую загрузку API только после того, как местоположение действительно обновлено, а не равно нулю.

struct ExploreView: View {
    @ObservedObject  var location: LocationManager
    @ObservedObject var fetcher: RestaurantFetcher
    
    init() {
        let location = LocationManager()  // << use only one instance
        self.location = location
        self.fetcher = RestaurantFetcher(locationManager: location)
        
        self.location.startUpdating()   // << do this only once
    }
    
    var body: some View {
        ScrollView (.vertical) {
            VStack {
                HStack {
                    Text("Discover ")
                        .font(.system(size: 28))
                        .fontWeight(.bold)
                        +  Text(" \(location.placemark?.locality ?? "")")
                        .font(.system(size: 28))
                        .fontWeight(.bold)
                    Spacer()
                }
                HStack {
                    SearchBar(text: .constant(""))
                }.padding(.top, 16)
                HStack {
                    Text("Featured Restaurants")
                        .font(.system(size: 24))
                        .fontWeight(.bold)
                    Spacer()
                    NavigationLink(
                        destination: FeaturedView(),
                        label: {
                            Text("View All")
                        })
                }.padding(.vertical, 30)
                HStack {
                    Text("All Cuisines")
                        .font(.system(size: 24))
                        .fontWeight(.bold)
                    Spacer()
                }
                Spacer()
            }.padding()
        }
    }
}

import Combine

public class RestaurantFetcher: ObservableObject {
    @Published var businesses = [RestaurantResponse]()
    private var locationManager: LocationManager
    
    var lat: String {
        return "\(locationManager.location?.coordinate.latitude ?? 0.0)"
    }
    
    var long: String {
        return "\(locationManager.location?.coordinate.longitude ?? 0.0)"
    }
    
    private var subscriber: AnyCancellable?
    init(locationManager: LocationManager) {
        self.locationManager = locationManager
        
        // listen for available location explicitly
        subscriber = locationManager.$location
            .debounce(for: 5, scheduler: DispatchQueue.main) // wait for 5 sec to avoid often reload
            .receive(on: DispatchQueue.main)
            .sink { [weak self] location in
                guard location != nil else { return }
                self?.load()
            }
    }
    
    func load() {
        print("\(locationManager.location?.coordinate.latitude ?? 0.0)")
        print("user latitude top of function")
        //Returns default values of 0.0
        let apikey = "APIKEY Here"
        let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(lat)&longitude=\(long)&radius=40000")!
        var request = URLRequest(url: url)
        request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"
        
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            do {
                if let d = data {
                    print("\(self.locationManager.location?.coordinate.longitude ?? 0.0)")
                    let decodedLists = try JSONDecoder().decode(BusinessesResponse.self, from: d)
                    
                    // Returns actual location coordinates
                    DispatchQueue.main.async {
                        self.businesses = decodedLists.restaurants
                    }
                } else {
                    print("No Data")
                }
            } catch {
                print ("Caught")
            }
        }.resume()
    }
}
person Asperi    schedule 18.11.2020
comment
Хороший, это именно то, что я искал, и похоже, что он соответствует SwiftUI 2.0. Есть ли шанс добиться аналогичного результата, используя встроенные обертки свойств? - person Matthew Morek; 18.11.2020