Обновить MapView с текущим местоположением на SwiftUI - PullRequest
0 голосов
/ 30 апреля 2020

Пытаясь обновить вид карты Project 14 из 100daysOfSwiftUI, чтобы показать мое текущее местоположение, проблема, которую я не могу увеличить, перемещается вокруг

У меня есть этот код, который я добавляю @Binding var currentLocation : CLLocationCoordinate2D и view.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

    }
    ...

ОБНОВЛЕНИЕ

спасибо за поддержку, которую я использую этим классом для вызова 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)
    }

}

Я просто хочу центрировать свое отображение карты на моем текущем местоположении, когда я нажмите кнопку

1 Ответ

2 голосов
/ 30 апреля 2020

Нет, где здесь вы когда-либо звоните locationManager.requestWhenInUseAuthorization(). Когда я это сделал (конечно, убедившись, что в Info.plist есть запись для NSLocationWhenInUseUsageDescription), он корректно обновил местоположение.

Например,

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

Это просто быстрое и грязное исправление, демонстрирующее, что оно работает. Но это не совсем правильно, потому что в первый раз, когда вы вызываете getCurrentLocation, если он должен запросить у пользователя разрешение, что он делает асинхронно, это означает, что у него еще не будет местоположения, когда вы доберетесь до lastLocation линия в вашей реализации. Это одноразовая вещь, но все же это не приемлемо. Вам нужно ваше CLLocationManagerDelegate обновление currentLocation, если это необходимо. Но, надеюсь, у вас достаточно здесь, чтобы диагностировать, почему ваше местоположение не фиксируется корректно CLLocationManager.


FWIW, вы можете подумать об использовании userTrackingMode из .follow, что устраняет необходимость всего этого ручного менеджера местоположения и 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()
        }
    }
}
...