在 SwiftUI 上使用当前位置更新 MapView

Update MapView with current location on SwiftUI

正在尝试更新 100daysOfSwiftUI 项目 14 的地图视图以显示我的当前位置,但我无法放大移动的问题

我有这段代码,我将 @Binding var currentLocation : CLLocationCoordinate2Dview.setCenter(currentLocation, animated: true) 添加到我的 MapView,所以我有一个发送那个值的按钮,视图实际上移动到该位置的速度很慢,但我可以离开再

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {

    @Binding var centerCoordinate: CLLocationCoordinate2D
    @Binding var selectedPlace: MKPointAnnotation?
    @Binding var showingPlaceDetails: Bool
    @Binding var currentLocation : CLLocationCoordinate2D

    var annotations: [MKPointAnnotation]

    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator
        return mapView
    }

    func updateUIView(_ view: MKMapView, context: Context) {

        if annotations.count != view.annotations.count {
            view.removeAnnotations(view.annotations)
            view.addAnnotations(annotations)
        }

        view.setCenter(currentLocation, animated: true)

    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

 class Coordinator: NSObject, MKMapViewDelegate{

    var parent: MapView
    init(_ parent: MapView) {
        self.parent = parent
    }

    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
         parent.centerCoordinate = mapView.centerCoordinate
     }

     func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
         let identifier = "PlaceMark"
         var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
         if annotationView == nil {
             annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
             annotationView?.canShowCallout = true
             annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)

         } else {
             annotationView?.annotation = annotation
         }

         return annotationView
     }

     func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
         guard let placemark = view.annotation as? MKPointAnnotation else {return}
         parent.selectedPlace = placemark
         parent.showingPlaceDetails = true

     }

    }
}

这是我的 swiftUI 视图

...
    @State private var currentLocation = CLLocationCoordinate2D()

    var body: some View {
        ZStack{

            MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, currentLocation: $currentLocation ,  annotations: locations)
           // MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations)
                .edgesIgnoringSafeArea(.all)
            VStack{
                Spacer()
                HStack{
                    Spacer()
                    Button(action: {
                        self.getCurrentLocation()
                    }){
                        ButtonIcon(icon: "location.fill")
                    }
                }
                .padding()
            }
        }
        .onAppear(perform: getCurrentLocation)
    }

    func getCurrentLocation() {

        let lat = locationManager.lastLocation?.coordinate.latitude ?? 0
        let log = locationManager.lastLocation?.coordinate.longitude ?? 0

        self.currentLocation.latitude = lat
        self.currentLocation.longitude = log

    }
    ...

更新

感谢支持我用这个class来调用locationManager.requestWhenInUseAuthorization()

import Foundation
import CoreLocation
import Combine

class LocationManager: NSObject, ObservableObject {

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

    @Published var locationStatus: CLAuthorizationStatus? {
        willSet {
            objectWillChange.send()
        }
    }

    @Published var lastLocation: CLLocation? {
        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"
        }

    }

    let objectWillChange = PassthroughSubject<Void, Never>()

    private let locationManager = CLLocationManager()
}

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

}

我只是想在我按下按钮时将我的地图视图置于我当前位置的中心

没有你在哪里打电话locationManager.requestWhenInUseAuthorization(). When I did that (of course, making sure the Info.plist had an entry for NSLocationWhenInUseUsageDescription),它正确地更新了位置。

例如

func getCurrentLocation() {
    if CLLocationManager.authorizationStatus() == .notDetermined {
        locationManager.requestWhenInUseAuthorization()
    }
    if let coordinate = locationManager.location?.coordinate {
        currentLocation = coordinate
    }
}

现在,这只是一个快速而肮脏的修复来证明它的工作原理。但这不太正确,因为第一次调用 getCurrentLocation 时,如果它必须请求用户许可,它是异步执行的,这意味着当您到达 lastLocation 行在您的实施中。这是一次性的事情,但仍然不能接受。如果需要,您需要 CLLocationManagerDelegate 更新 currentLocation。但希望你已经有足够的知识来诊断为什么你的位置没有被 CLLocationManager.

正确捕获

FWIW,您可以考虑使用 .followuserTrackingMode,这样就不需要所有这些手动位置管理器和 currentLocation 东西。我要提到的一个警告(因为我一天花了几个小时试图诊断这种奇怪的行为)是,如果你用以下方式初始化地图视图,userTrackingMode 不起作用:

let mapView = MKMapView()

但如果你给它一些框架,它就会起作用,例如:

let mapView = MKMapView(frame: UIScreen.main.bounds)

因此,对于用户跟踪模式:

struct MapView: UIViewRepresentable {
    @Binding var userTrackingMode: MKUserTrackingMode

    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView(frame: UIScreen.main.bounds)
        mapView.delegate = context.coordinator
        mapView.userTrackingMode = userTrackingMode

        return mapView
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        view.userTrackingMode = userTrackingMode
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, MKMapViewDelegate {
        var parent: MapView

        init(_ parent: MapView) {
            self.parent = parent
        }

        // MARK: - MKMapViewDelegate

        func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
            DispatchQueue.main.async {
                self.parent.$userTrackingMode.wrappedValue = mode
            }
        }

        // note, implementation of `mapView(_:viewFor:)` is generally not needed if we register annotation view class
    }
}

然后,我们可以在用户跟踪关闭时出现一个“关注”按钮(以便您可以重新打开它):

struct ContentView: View {
    @State var userTrackingMode: MKUserTrackingMode = .follow

    private var locationManager = CLLocationManager()

    var body: some View {
        ZStack {
            MapView(userTrackingMode: $userTrackingMode)
                .edgesIgnoringSafeArea(.all)

            VStack {
                HStack {
                    Spacer()

                    if self.userTrackingMode == .none {
                        Button(action: {
                            self.userTrackingMode = .follow
                        }) {
                            Text("Follow")
                        }.padding()
                    }
                }

                Spacer()
            }
        }.onAppear { self.requestAuthorization() }
    }

    func requestAuthorization() {
        if CLLocationManager.authorizationStatus() == .notDetermined {
            locationManager.requestWhenInUseAuthorization()
        }
    }
}