无法从用户位置 SwiftUi 更新 CLLocation()

Posted

技术标签:

【中文标题】无法从用户位置 SwiftUi 更新 CLLocation()【英文标题】:Cant Update CLLocation() from user Location SwiftUi 【发布时间】:2021-11-04 20:16:36 【问题描述】:

你好,我真的是 ios 新手,SwiftUi 所以如果我的格式有误请不要恨我哈哈。我无法将用户位置坐标转换为美国地址。我已经完成了大部分代码,但出于某种原因在线 109 我创建了 @Published var location 并将其设置到某个随机位置,然后我尝试在线更新它 l72 但由于某种原因它只使用我在109 线上传递的第一个位置。我喜欢在用户授予权限后更新和使用用户当前位置。感谢您的任何建议:)

//
//  ContentView.swift
//  Parked
//
//  Created by Luke Jamison on 11/2/21.
//

import SwiftUI
import MapKit
import CoreLocation
import CoreLocationUI

struct ContentView: View 
    static let sharedV = ContentView()
    @StateObject private var viewModel = ContentViewModel()
    
    
    @State var addressData: String = ""
    @State private var showingImagePicker = false
    @State private var image: Image?
    @State private var inputImage: UIImage?
    //@State var geo: Any = locationManager.location
    var body: some View 
        NavigationView 
            VStack 
                Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
                    .onAppear 
                        viewModel.checkIFLocationServicesisEnabled()
                        setData()
                        //viewModel.runSetDatafunc()
                    .frame(maxWidth: .infinity, maxHeight: .infinity).cornerRadius(20).padding()
                
                Form 
                    Section(header: Text("Add Location Details")) 
                        Text(addressData)
                        Menu 
                            Button(action: 
                                self.showingImagePicker = true
                            ) 
                                Label("Take Picture", systemImage: "camera.shutter.button")
                            
                            image?.resizable()
                                .frame(width: 250, height: 250)
                                .clipShape(Circle())
                                .overlay(Circle().stroke(Color.white, lineWidth: 4))
                                .shadow(radius: 10)
                            
                            
                         label: 
                            Label("Add Image", systemImage: "photo.on.rectangle.angled")
                        
                        
                    
                    if image != nil 
                        Section(header: Text("Image Taken")) 
                            ZStack 
                                Rectangle().fill(Color.secondary)
                                
                                if image != nil 
                                    image?.resizable().scaledToFit()
                                 else 
                                    
                                
                            
                            Button(action: 
                                image = nil
                            ) 
                                Label("Remove Picture", systemImage: "trash.fill")
                            
                        
                     else 
                        
                    
                .sheet(isPresented: $showingImagePicker, onDismiss: loadImage) 
                    ImagePicker(image: self.$inputImage)
                    
                .navigationBarTitle("Parked", displayMode: .automatic)
            
        .navigationViewStyle(.stack)
    
    
    func loadImage() 
        guard let inputImage = inputImage else  return 
        image = Image(uiImage: inputImage)
        
    
    func setData() 
        ContentViewModel.shared.resolveLoactionName(with: viewModel.location)  [self] locationName in
            self.addressData = locationName
        
    
    
    
    
    struct ContentView_Previews: PreviewProvider 
        static var previews: some View 
            ContentView()
        
    
    
    final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate 
        static let shared = ContentViewModel()
        
        @Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 33.4, longitude: -117.4), span:
                                                    MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
        
        
        @Published var address = ""
        @Published var location = CLLocation.init(latitude: 33, longitude: -117)
        
        var locationManager: CLLocationManager?
        
        func checkIFLocationServicesisEnabled() 
            if CLLocationManager.locationServicesEnabled() 
                locationManager = CLLocationManager()
                locationManager!.delegate = self
                //locationManager?.desiredAccuracy = kCLLocationAccuracyBest
             else 
                print("this is off turn in the settings...")
            
        
        
        public func resolveLoactionName(with location: CLLocation, completion: @escaping ((String) -> Void)) 
            
            let geocoder = CLGeocoder()
            geocoder.reverseGeocodeLocation(location, preferredLocale: .current)  placemarks, error in
                guard let place = placemarks?.first, error == nil else 
                    completion("")
                    return
                
                print(place)
                var name = ""
                
                if let locality = place.subThoroughfare 
                    name += locality
                
                
                if let street = place.thoroughfare 
                    name += " \(street)"
                
                
                if let city = place.locality 
                    name += " \(city)"
                
                
                if let adminRegion = place.administrativeArea 
                    name += ", \(adminRegion)"
                
                
                if let zipCode = place.postalCode 
                    name += " \(zipCode)"
                
                
                completion(name)
            
        
        
         private func checkLocationAuth() 
            guard let locationManager = locationManager else  return 
            
            switch locationManager.authorizationStatus 
                
            case .notDetermined:
                locationManager.requestWhenInUseAuthorization()
            case .restricted:
                print("location is restricted")
            case .denied:
                print("location is denied in settings for app")
            case .authorizedAlways, .authorizedWhenInUse:
                region = MKCoordinateRegion(center: locationManager.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
                ContentView.sharedV.setData()
                self.location = CLLocation(latitude: region.self.center.latitude, longitude: region.self.center.longitude)
                print(location)
                //runSetDatafunc()
                //address = "\(locationManager.location!.coordinate.latitude), \(locationManager.location!.coordinate.longitude)"
                /*geocode(latitude: region.center.latitude, longitude: region.center.longitude)  placemark, error in
                    let placemark = placemark?.first
                    //print(placemark?.description)
                    //self.address = "\(placemark?.thoroughfare), \(placemark?.locality), \(placemark?.administrativeArea)"
                */
            @unknown default:
                break
            
            
        
        public func runSetDatafunc() 
           
        
        func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) 
            checkLocationAuth()
            
        
        
    
    

【问题讨论】:

你在这里有点过分了;您有一个共享的“单例”视图模型实例;不要那样做。您有一个共享的视图实例。绝对不要那样做; SwiftUI 视图是结构和临时的。每次视图更新时都会重新创建它们。您正在请求“使用时”但检查“始终”位置身份验证。获取位置是一个异步操作;所以你需要请求位置或调用startUpdatingLocation并在委托方法中处理位置更新。 如果您致电startUpdatingLocation,您将每秒钟左右获得一次更新。以该速率发送反向地理编码请求将导致失败,因为此服务受速率限制。 【参考方案1】:

不幸的是,由于 SwiftUI 视图的瞬态特性,您绝对不会有运气从 View 中创建单例。但是,您不需要它,因为您已经发布了您的 regionlocation,您的 View 将根据这些信息进行更新。

这是您的代码的简化/重构版本:


struct ContentView: View 
    @StateObject private var viewModel = ContentViewModel()
    
    var body: some View 
        VStack 
            Text(viewModel.address)
            Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
                .onAppear 
                    viewModel.checkIfLocationServicesisEnabled()
                
        
    


final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate 
    static let shared = ContentViewModel()
    
    @Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 33.4, longitude: -117.4), span:
                                                MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
    @Published var address = ""
    @Published var location = CLLocation.init(latitude: 33, longitude: -117)
    
    private var lastResolved : CLLocation = .init()
    
    var locationManager: CLLocationManager?
    
    func checkIfLocationServicesisEnabled() 
        if CLLocationManager.locationServicesEnabled() 
            locationManager = CLLocationManager()
            locationManager?.startUpdatingLocation()
            locationManager?.delegate = self
            //locationManager?.desiredAccuracy = kCLLocationAccuracyBest
         else 
            print("this is off turn in the settings...")
        
    
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
        guard let last = locations.last else  return 
        
        if last.distance(from: lastResolved) > 200 
            resolveLocationName(with: last)  address in
                self.address = address
                self.lastResolved = last
            
        
        
        self.location = last
    
    
    public func resolveLocationName(with location: CLLocation, completion: @escaping ((String) -> Void)) 
        
        let geocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(location, preferredLocale: .current)  placemarks, error in
            guard let place = placemarks?.first, error == nil else 
                completion("")
                return
            
            print(place)
            var name = ""
            
            if let locality = place.subThoroughfare 
                name += locality
            
            
            if let street = place.thoroughfare 
                name += " \(street)"
            
            
            if let city = place.locality 
                name += " \(city)"
            
            
            if let adminRegion = place.administrativeArea 
                name += ", \(adminRegion)"
            
            
            if let zipCode = place.postalCode 
                name += " \(zipCode)"
            
            
            completion(name)
        
    
    
    private func checkLocationAuth() 
        guard let locationManager = locationManager else  return 
        
        switch locationManager.authorizationStatus 
            
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .restricted:
            print("location is restricted")
        case .denied:
            print("location is denied in settings for app")
        case .authorizedAlways, .authorizedWhenInUse:
            region = MKCoordinateRegion(center: locationManager.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
            self.location = CLLocation(latitude: region.self.center.latitude, longitude: region.self.center.longitude)
            print(location)
        @unknown default:
            break
        
    
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) 
        checkLocationAuth()
    

请注意,我正在调用 startUpdatingLocation() 并实现 didUpdateLocations - 这些将允许您获取/更新用户的位置。

在模拟器中,您可以使用Features->Location菜单来模拟不同的点。您会看到它解析不同的位置并在屏幕顶​​部显示地址。

【讨论】:

在不注意合理移动的情况下从didUpdateLocations 调用reverseGeocodeLocation 是个坏主意;您将超过速率限制 好点——使用位置约束更新,并将在答案中向 OP 添加注释。 @jnpdx 非常感谢!我看到的唯一问题是Cannot find 'lastResolved' in scope @Luke 抱歉——我更新了一部分而不是另一部分,忘记添加属性。查看更新版本。 @Luke 如果这回答了问题,您可以使用绿色复选标记将其标记为正确吗?

以上是关于无法从用户位置 SwiftUi 更新 CLLocation()的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI Live 位置更新

CLLocationManager 奇怪的行为

EXC_BAD_INSTRUCTION 在 Swift 中为 locationManager 崩溃(经理:CLLocationManager,didUpdateLocations 位置:[CLLoca

点击按钮时SwiftUI获取用户的位置

SwiftUI 中的底部优先滚动

iOS - 位置更改时 SwiftUI 更新文本