首页 > 解决方案 > SwiftUI - 应用程序进入前台时获取位置和调用函数

问题描述

我是 SwiftUI 的新手,我在一些我认为相当简单的事情上遇到了困难。我创建了一个显示用户当前位置的应用程序。如果用户将应用程序置于后台,并且用户的物理位置发生了变化,则应用程序在进入前台时不会显示新的位置。如果我杀死该应用程序,然后重新启动,它会显示新位置。

当检测到位置更改时,我还想调用一个函数从 Firebase 获取更新的数据集。这是我正在使用的代码的简化版本。请让我知道我错过了什么。我想我应该使用更多的组合功能来实现这个结果,但我不确定如何?

我的关键问题是:“我如何在我的视图中检测位置变化并调用一个函数作为该位置变化的结果?”

MyView.swift

import SwiftUI

struct MyView: View {

    @ObservedObject 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)")
            }
            Text("place: \(locationManager.placemark?.thoroughfare ?? "")") 
        }
     
    }
    
    func refreshData() {
        // call firebase listener with new location data
    }

}

LocationManager.swift

import Foundation
import CoreLocation
import Combine

class LocationManager: NSObject, ObservableObject {
    
    let objectWillChange = PassthroughSubject<Void, Never>()
    
    private let locationManager = CLLocationManager()
    
    override init() {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        self.locationManager.requestWhenInUseAuthorization()
        self.locationManager.startUpdatingLocation()
    }
    
    @Published var locationStatus: CLAuthorizationStatus? {
        willSet {
            objectWillChange.send()
        }
    }
    
    @Published var lastLocation: CLLocation? {
        willSet {
            objectWillChange.send()
        }
    }
    
    @Published var placemark: CLPlacemark? {
        willSet {
            objectWillChange.send()
        }
    }
    
    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"
        }
        
    }
    
}

extension LocationManager: CLLocationManagerDelegate {
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        self.locationStatus = status
        print(#function, statusString)
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        self.lastLocation = location
        print(#function, location)
        
        let geocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
            if (error != nil){
                print("Error in reverseGeocode: \(error)")
                return
            }
            
            let placemark = placemarks! as [CLPlacemark]
            
            if placemark.count > 0 {
                let placemark = placemarks![0]
                self.placemark = placemark
            }
        }
        locationManager.stopUpdatingLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        if let error = error as? CLError, error.code == .denied {
            // Location updates are not authorized.
            locationManager.stopUpdatingLocation()
            return
        }
        // Notify the user of any errors.
    }
    
}

标签: swiftuicore-locationcombine

解决方案


您可以订阅 LocationManager 类中的 lastLocation 属性,以便在发生更改时得到通知。使用 Combine,您可以像这样实现:

// inside the location manager class
private var cancellables: [AnyCancellable] = [] // new property to keep track of your subscriptions

// inside the initialiser of the class
$lastLocation
    .receive(on: RunLoop.main) // main thread so view gets an update
    .sink { lastLocation in // subscribe to the published property. Anytime it changes you will get notified
       refreshData()
       // do more with the new value of last location or not :)
    }
    .store(in: cancellabels) // store subscription in property so it doesn’t go out of scope immediately 

此外,在您的视图中,请确保绕过计算属性并直接订阅已发布的位置属性。根据我的经验,计算属性和订阅不能很好地协同工作。在您的文本视图中直接引用位置坐标:

Text(locationManager.lastLocation?.coordinate.latitude ?? 0)

推荐阅读