SwiftUI - 获取用户坐标以传递 API 调用

Posted

技术标签:

【中文标题】SwiftUI - 获取用户坐标以传递 API 调用【英文标题】:SwiftUI - Get User's coordinates to pass in API call 【发布时间】:2020-08-26 20:09:50 【问题描述】:

这个问题已经困扰我好几个月了,我相信这归结为我使用了错误的结构和程序。

我正在尝试对 Yelp 的 API 进行 API 调用,并为用户的纬度/经度传递变量。我能够根据我当前的 LocationManager 获取纬度/经度,但是当似乎纬度/经度似乎仅在 API 调用之后才可用时,因此 API 获取纬度/经度的默认值 0.0 .

我是一个初学者成立了吗?

下面是我的 LocationManager 和 ExploreView

位置管理器

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()
    

【问题讨论】:

你有没有设法解决它? 【参考方案1】:

尝试以下修改后的代码(我需要进行一些复制,所以请注意 - 可能有一些拼写错误)。

主要思想是订阅 LocationManager 更新的位置发布者,以侦听位置的显式变化,并仅在位置真正更新而不是 nil 后执行下一次 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()
    

【讨论】:

不错,这正是我想要的,它看起来符合 SwiftUI 2.0。有没有机会使用内置的属性包装器来实现类似的结果?

以上是关于SwiftUI - 获取用户坐标以传递 API 调用的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI MVVM 将变量从 ViewModel 传递到 API 服务

SwiftUI - 如何通过视图模型完成调用函数

如何跨视图传递 CoreData 对象

Swiftui + 核心位置:地图无法以用户为中心

SwiftUI 获取 TextField 或任何视图的坐标

将变量从 UIHostingController 传递到 SWIFTUI 视图