如何在没有 ViewController 的情况下使用 SwiftUI 获取当前位置?
How to get Current Location using SwiftUI, without ViewControllers?
我在我的项目中准备了以下 class 来检索用户当前位置:
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 中使用的方法的情况下获得 exposedLocation(在这种情况下,使用 guard、let 和 return 当然会产生各种错误,因为我我不应该在这种情况下使用 returns,如果我做对了)。
关于如何实现这一目标的任何提示?
我想在按下按钮时获取用户位置(目前我只使用了模型数据)。
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()
}
}
您可以通过实施 ObservableObject
协议为您的 LocationManager
创建一个 StateObject
。
使用 @Published
属性,您可以创建一个发布者对象,当该对象内部发生变化时,该对象会通知观察者(在本例中是您的视图)。
这就是为什么在我的 LocationManager 中我将 @Published
属性添加到那些 var:
locationStatus: CLAuthorizationStatus?
它包含从 didChangeAuthorization
委托方法 接收到的值
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)
}
}
查看
在您看来,您只需创建一个标记为 @StateObject
的 LocationManager
实例
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()
}
}
我在我的项目中准备了以下 class 来检索用户当前位置:
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 中使用的方法的情况下获得 exposedLocation(在这种情况下,使用 guard、let 和 return 当然会产生各种错误,因为我我不应该在这种情况下使用 returns,如果我做对了)。 关于如何实现这一目标的任何提示? 我想在按下按钮时获取用户位置(目前我只使用了模型数据)。
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()
}
}
您可以通过实施 ObservableObject
协议为您的 LocationManager
创建一个 StateObject
。
使用 @Published
属性,您可以创建一个发布者对象,当该对象内部发生变化时,该对象会通知观察者(在本例中是您的视图)。
这就是为什么在我的 LocationManager 中我将 @Published
属性添加到那些 var:
locationStatus: CLAuthorizationStatus?
它包含从didChangeAuthorization
委托方法 接收到的值
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)
}
}
查看
在您看来,您只需创建一个标记为 @StateObject
LocationManager
实例
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()
}
}