【问题标题】:How to get Current Location using SwiftUI, without ViewControllers?如何在没有 ViewControllers 的情况下使用 SwiftUI 获取当前位置?
【发布时间】:2020-01-01 01:27:01
【问题描述】:

我在我的项目中准备了以下类来检索用户当前位置:

LocationManager.swift

import Foundation
import CoreLocation


class LocationManager: NSObject {

    // - Private
    private let locationManager = CLLocationManager()

    // - API
    public var exposedLocation: CLLocation? {
        return self.locationManager.location
    }

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

// MARK: - Core Location Delegate 
extension LocationManager: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager,
                         didChangeAuthorization status:      CLAuthorizationStatus) {

        switch status {

            case .notDetermined         : print("notDetermined")          // location permission not asked for yet
            case .authorizedWhenInUse   : print("authorizedWhenInUse")  // location authorized
            case .authorizedAlways      : print("authorizedAlways")     // location authorized
            case .restricted            : print("restricted")           // TODO: handle
            case .denied                : print("denied")               // TODO: handle
            default                     : print("unknown")              // TODO: handle
        }
    }
}

// MARK: - Get Placemark
extension LocationManager {

    func getPlace(for location: CLLocation,
              completion: @escaping (CLPlacemark?) -> Void) {

        let geocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(location) { placemarks, error in

            guard error == nil else {
                print("*** Error in \(#function): \  (error!.localizedDescription)")
                completion(nil)
                return
            }

            guard let placemark = placemarks?[0] else {
                print("*** Error in \(#function): placemark is nil")
                completion(nil)
                return
            }

            completion(placemark)
        }
    }
}

但我不确定如何在使用 SwiftUI 时从我的 ContentView 文件中使用它。 如果不使用在标准 ViewController 中使用的方法,我应该如何获得暴露位置(在这种情况下,使用 guard、let 和 return 当然会产生各种错误,因为我不应该在这个上下文,如果我做对了)。 关于如何实现这一目标的任何提示? 我想在按下按钮时获取用户位置(目前我只使用了模型数据)。

ContentView.swift
import SwiftUI

struct Location: Identifiable {
    // When conforming to the protocol Identifiable we have to to   implement a variable called id however this variable does not have to be an Int. The protocol only requires that the type of the variable id is actually Hashable.
    // Note: Int, Double, String and a lot more types are Hashable
    let id: Int

    let country: String
    let state: String
    let town: String
}

struct ContentView: View {
    // let’s make our variable a @State variable so that as soon as we change its value (by for eexample adding new elements) our view updates automagically.
    @State var locationList = [
    Location(id: 0, country: "Italy", state: "", town: "Finale Emilia"),
    Location(id: 1, country: "Italy", state: "", town: "Bologna"),
    Location(id: 2, country: "Italy", state: "", town: "Modena"),
    Location(id: 3, country: "Italy", state: "", town: "Reggio Emilia"),
    Location(id: 4, country: "USA", state: "CA", town: "Los Angeles")
    ]

    // - Constants
    private let locationManager = LocationManager()

    // THIS IS NOT POSSIBLE WITH SWIFTUI AND GENERATES ERRORS
    guard let exposedLocation = self.locationManager.exposedLocation else {
        print("*** Error in \(#function): exposedLocation is nil")
        return
    }

    var body: some View {
        // Whenever we use a List based of an Array we have to let the List know how to identify each row as unique
        // When confirming to the Identifiable protocol we no longer have to explicitly tell the List how the elements in our Array (which are conforming to that protocol) are uniquely identified
    NavigationView {
        // let’s add a title to our Navigation view and make sure you always do so on the first child view inside of your Navigation view
        List(locationList) { location in
            NavigationLink(destination: LocationDetail(location: location)) {
                HStack {
                   Text(location.country)
                   Text(location.town).foregroundColor(.blue)
                }
        }
    }
    .navigationBarTitle(Text("Location"))
    .navigationBarItems(
        trailing: Button(action: addLocation, label: { Text("Add") }))
    }
}

    func addLocation() {
      // We are using the native .randomElement() function of an Array to get a random element. The returned element however is optional. That is because in the case of the Array being empty that function would return nil. That’s why we append the returned value only in the case it doesn’t return nil.
      if let randomLocation = locationList.randomElement() {
        locationList.append(randomLocation)
      }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

【问题讨论】:

  • 我添加了一个关于如何检索暴露位置的问题的回复。无论如何,在您的代码中,您忘记添加 startUpdatingLocation 和获取新位置的相关方法。无论如何,我还没有实现 getPlace 方法,如果您对此有疑问,请询问(或者最好打开一个新问题)

标签: ios swiftui core-location cllocationmanager


【解决方案1】:

您可以通过实现ObservableObject 协议来创建LocationManagerStateObject

使用@Published 属性,您可以创建一个发布者对象,当该对象内部发生变化时通知观察者(在本例中是您的视图)。

这就是为什么在我的 LocationManager 中我将 @Published 属性添加到这些 var:

  1. locationStatus: CLAuthorizationStatus? 它包含从 didChangeAuthorization 委托方法接收到的值
  2. lastLocation: CLLocation? 它包含由 didUpdateLocations 委托方法计算的最后一个位置

位置管理器

import Foundation
import CoreLocation
import Combine

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {

    private let locationManager = CLLocationManager()
    @Published var locationStatus: CLAuthorizationStatus?
    @Published var lastLocation: CLLocation?

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

   
    
    var statusString: String {
        guard let status = locationStatus else {
            return "unknown"
        }
        
        switch status {
        case .notDetermined: return "notDetermined"
        case .authorizedWhenInUse: return "authorizedWhenInUse"
        case .authorizedAlways: return "authorizedAlways"
        case .restricted: return "restricted"
        case .denied: return "denied"
        default: return "unknown"
        }
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        locationStatus = status
        print(#function, statusString)
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        lastLocation = location
        print(#function, location)
    }
}

查看

在您看来,您只需创建一个标记为@StateObjectLocationManager 实例

import SwiftUI

struct MyView: View {
    
    @StateObject var locationManager = LocationManager()
    
    var userLatitude: String {
        return "\(locationManager.lastLocation?.coordinate.latitude ?? 0)"
    }
    
    var userLongitude: String {
        return "\(locationManager.lastLocation?.coordinate.longitude ?? 0)"
    }
    
    var body: some View {
        VStack {
            Text("location status: \(locationManager.statusString)")
            HStack {
                Text("latitude: \(userLatitude)")
                Text("longitude: \(userLongitude)")
            }
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}

【讨论】:

  • 非常感谢 Giuseppe,你拯救了我的一天! :-) 我正在尝试找出组合,也许你可以使用 @BindableObject 或 @EnvironmentObject 来实现相同的目标,但我绝对需要研究这个......
  • 谢谢!这是我见过的使用 SwiftUI 的 GPS 的最佳解决方案
  • 我收到错误:在 super.init 调用时未初始化属性“self.locationStatus”。为什么?
  • 应用首次启动时会弹出两次询问位置权限的窗口,选择后会再次弹出询问权限的窗口
  • 很好的解决方案!但是有必要在 Info.plist 中添加参数。见这里:stackoverflow.com/questions/46193797/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-05
  • 1970-01-01
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多