Show user location on map SwiftUI
Asked Answered
A

8

8

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.

Allodial answered 12/6, 2019 at 1:29 Comment(2)
Have you tried testing this on the real device? The simulator can be fiddly when it comes to Core Location - also make sure that the current scheme allows location simulation: Current Scheme -> Edit -> Run -> Options ->Allow Location SimulationCongou
Yes I have done that and made sure I have entered a location. I also tested it on a mobile device. I also have added the permissions in the info.plistAllodial
F
6

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)

        })
    }

}
Filet answered 12/6, 2019 at 7:30 Comment(2)
Thank you Harshal! That works fine. However I can only run it under dispatch. I am not sure whether that is a good practice or not.Allodial
Thank you Harshal. I notice your code also works if I place it inside the makeUIView method instead of updateUIView, so that it only executes once during creation instead continuing to center on the user's location. Thanks for your post!!!Philanthropist
C
2

The above codes will cause the app crash because of the inappropriate permission request. Use the below code for better performance and on-time permission.

struct MapView: UIViewRepresentable {
    let locationManager = CLLocationManager()        
    
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }
    
    func updateUIView(_ view: MKMapView, context: Context) {
        view.showsUserLocation = true
        let status = CLLocationManager.authorizationStatus()
        locationManager.requestAlwaysAuthorization()
        locationManager.requestWhenInUseAuthorization()
        
        if status == .authorizedAlways || status == .authorizedWhenInUse {
            locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
            locationManager.startUpdatingLocation()
            let location: CLLocationCoordinate2D = locationManager.location!.coordinate
            let span = MKCoordinateSpan(latitudeDelta: 0.009, longitudeDelta: 0.009)
            let region = MKCoordinateRegion(center: location, span: span)
            view.setRegion(region, animated: true)
        }
    }
    
}
Camellia answered 30/11, 2019 at 17:14 Comment(4)
When locationManager is defined inside updateUIView function, the dialog quickly disappears.Deoxygenate
Works great, but the map doesn't stay centered on the marker. Is there a way to always keep the marker centered?Danonorwegian
@VolkanPaksoy It sounds like this solution has that problem because the CLLocationManager instance here is instantiated at the method level, rather than in a scope with a longer lifecycle (like the class itself). When the method ends, the locationManager reference is no longer needed, and the locationManager instance gets cleaned up by the reference counter. A similar problem was answered in this issue: #7889396Asphalt
@VolkanPaksoy the problem solved by changing the scope of locationManagerCamellia
Z
2

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)
  }
}
Zoller answered 27/2, 2020 at 19:24 Comment(0)
W
1

try this one:

struct OriginMap : UIViewRepresentable {




 func makeUIView(context: UIViewRepresentableContext<OriginMap>) -> MKMapView{
    MKMapView()
}




 func updateUIView(_ mapView: MKMapView, context: Context) {
    let locationManager = CLLocationManager()


    mapView.showsUserLocation = true

    locationManager.requestAlwaysAuthorization()

    locationManager.requestWhenInUseAuthorization()

    if CLLocationManager.locationServicesEnabled() {
        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()

        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
            let locValue:CLLocationCoordinate2D = locationManager.location!.coordinate


            let coordinate = CLLocationCoordinate2D(
                latitude: locValue.latitude, longitude: locValue.longitude)
            Datas.currentLocation = coordinate


            let span = MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002)
            let region = MKCoordinateRegion(center: coordinate, span: span)
            mapView.setRegion(region, animated: true)


        })
    }}
Washery answered 24/7, 2019 at 8:39 Comment(1)
This line doesn't work for me: Datas.currentLocation = coordinate What is "Datas" here?Deoxygenate
C
0

Here is the code for SwiftUI2

import MapKit
import SwiftUI

struct MapView: View {
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.4353, longitude: 35.23442), span: MKCoordinateSpan(latitudeDelta: 0.15, longitudeDelta: 0.15))
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

you can also check this URL for more info about other useful features added to SwiftUI2.

Camellia answered 1/7, 2020 at 7:45 Comment(0)
N
0
import SwiftUI
import MapKit

struct ContentView: View {
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: -23.506403, longitude: -46.1509555),
        span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))

    var body: some View {
        Map(coordinateRegion: $region, showsUserLocation: true)
    }
}
Nonunionism answered 2/4, 2021 at 20:43 Comment(0)
M
-1

Available for XCode 12.

import SwiftUI
import MapKit

struct ContentView: View {
    
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.321127, longitude: -122.047790),
        span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
    
    var body: some View {
        Map(coordinateRegion: $region)
    }
}
Massarelli answered 23/6, 2020 at 13:40 Comment(1)
The question is about showing the user location, not just the mapDimissory
I
-1

Using this you can find users region

Text(Locale.current.localizedString(forRegionCode: Locale.current.regionCode!) ?? "")
                    .font(.system(size: 30))
                    .fontWeight(.bold)
Intercessory answered 17/10, 2020 at 15:11 Comment(1)
No you can't. This does not address the OP's questionRavo

© 2022 - 2024 — McMap. All rights reserved.