I am trying to load a map and have the initial location on user's location as well as showing the user's location on the map using SwiftUI. I don't know how to do that in SwiftUI.
I have tried to put 'view.showsUserLocation = true' in updateUIView function but it is not working.
import SwiftUI
import MapKit
struct MapView : UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
view.showsUserLocation = true
let coordinate = CLLocationCoordinate2D(
latitude: 34.011286, longitude: -116.166868)
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: view.userLocation.location?.coordinate ?? coordinate, span: span)
view.setRegion(region, animated: true)
}
}
The location comes nil when I tried to print it.
The function requestLocation() uses the locationManager. requestLocation() function and is intended to be called when the LocationButton is tapped inside the SwiftUI View. The function locationManager(_:didUpdateLocations:) is implemented to handle any updates received from Core Location.
This takes three steps: Create some sort of state that will track the coordinates being shown by the map, using MKCoordinateRegion to track the center and zoom level of the map. Prepare an array of locations to use for your annotations. Decide how you want them to be shown on your map.
MKCoordinateRegion. A rectangular geographic region that centers around a specific latitude and longitude.
Add key Privacy - Location When In Use Usage Description
and description in Info.plist
Then try with updating below function:
func updateUIView(_ view: MKMapView, context: Context) {
view.showsUserLocation = true
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
// self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locationManager.startUpdatingLocation()
//Temporary fix: App crashes as it may execute before getting users current location
//Try to run on device without DispatchQueue
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
let locValue:CLLocationCoordinate2D = self.locationManager.location!.coordinate
print("CURRENT LOCATION = \(locValue.latitude) \(locValue.longitude)")
let coordinate = CLLocationCoordinate2D(
latitude: locValue.latitude, longitude: locValue.longitude)
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
})
}
}
This is the cleanest way I have found to setup CoreLocation and MapView. First, you have to create an UIViewRepresentable to use CoreLocation in SwiftUI.
Do not forget to enable in your Info.plist file this Privacy with a message:
Privacy - Location Always and When In Use Usage Description
Privacy - Location When In Use Usage Description
Privacy - Location Always Usage Description
import SwiftUI
import MapKit
// MARK: Struct that handle the map
struct MapView: UIViewRepresentable {
@Binding var locationManager: CLLocationManager
@Binding var showMapAlert: Bool
let map = MKMapView()
///Creating map view at startup
func makeUIView(context: Context) -> MKMapView {
locationManager.delegate = context.coordinator
return map
}
func updateUIView(_ view: MKMapView, context: Context) {
map.showsUserLocation = true
map.userTrackingMode = .follow
}
///Use class Coordinator method
func makeCoordinator() -> MapView.Coordinator {
return Coordinator(mapView: self)
}
//MARK: - Core Location manager delegate
class Coordinator: NSObject, CLLocationManagerDelegate {
var mapView: MapView
init(mapView: MapView) {
self.mapView = mapView
}
///Switch between user location status
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
break
case .denied:
mapView.showMapAlert.toggle()
return
case .notDetermined:
mapView.locationManager.requestWhenInUseAuthorization()
return
case .authorizedWhenInUse:
return
case .authorizedAlways:
mapView.locationManager.allowsBackgroundLocationUpdates = true
mapView.locationManager.pausesLocationUpdatesAutomatically = false
return
@unknown default:
break
}
mapView.locationManager.startUpdatingLocation()
}
}
}
}
This is the way I use it in my SwiftUI view. An alert is toggle in case the permission is denied in the Coordinator class switch:
import SwiftUI
import CoreLocation
// MARK: View that shows map to users
struct HomeView: View {
@State var locationManager = CLLocationManager()
@State var showMapAlert = false
var body: some View {
MapView(locationManager: $locationManager, showMapAlert: $showMapAlert)
.alert(isPresented: $showMapAlert) {
Alert(title: Text("Location access denied"),
message: Text("Your location is needed"),
primaryButton: .cancel(),
secondaryButton: .default(Text("Settings"),
action: { self.goToDeviceSettings() }))
}
}
}
extension HomeView {
///Path to device settings if location is disabled
func goToDeviceSettings() {
guard let url = URL.init(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With