Reputation: 595
Trying to update the mapview of the Project 14 of 100daysOfSwiftUI to show my current location, the problem i can´t zoom in move around
i have this code i add @Binding var currentLocation : CLLocationCoordinate2D
and view.setCenter(currentLocation, animated: true)
to my MapView so i have a button that send thats value and the view actually move so slow to the location but then i can move away anymore
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
}
}
}
an this is my swiftUI view
...
@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
}
...
UPDATE
thanks for the support I using this class to call 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)
}
}
i just want to center my mapview on my current location when i press the button
Upvotes: 0
Views: 2994
Reputation: 437372
No where here do you ever call locationManager.requestWhenInUseAuthorization()
. When I did that (of course, making sure the Info.plist
had an entry for NSLocationWhenInUseUsageDescription
), it updated the location correctly.
E.g.
func getCurrentLocation() {
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
if let coordinate = locationManager.location?.coordinate {
currentLocation = coordinate
}
}
Now, this is just a quick and dirty fix to demonstrate that it works. But it’s not quite right, because the first time you call getCurrentLocation
, if it has to ask the user for permission, which it does asynchronously, which means that it won’t yet have a location when you get to the lastLocation
line in your implementation. This is a one time thing, but still, it’s not acceptable. You’d want your CLLocationManagerDelegate
update currentLocation
if needed. But hopefully you’ve got enough here to diagnose why your location is not being captured correctly by the CLLocationManager
.
FWIW, you might consider using a userTrackingMode
of .follow
, which obviates the need for all of this manual location manager and currentLocation
stuff. The one caveat I’ll mention (because I spent hours one day trying to diagnose this curious behavior), is that the userTrackingMode
doesn’t work if you initialize your map view with:
let mapView = MKMapView()
But it works if you do give it some frame, e.g.:
let mapView = MKMapView(frame: UIScreen.main.bounds)
So, for user tracking mode:
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
}
}
And then, we can have a “follow” button that appears when user tracking is turned off (so that you can turn it back on):
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()
}
}
}
Upvotes: 2