UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to center map to users current location?

Forums > SwiftUI

Hi all, I'm using MapKit in SwiftUI to display a map with the current location of the user. I have this code so far, but the problem is that I have to manually specify the region.

So my question is, How can I make the map always center around the current location of the user?

I'm not that experienced yet, so I hope you will have some patience with me


struct ContentView: View {

    var body: some View {
        Home()
    }
}

struct Home: View{

    @State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.785834, longitude: -122.406417), latitudinalMeters: 1, longitudinalMeters: 1)

    @State var tracking : MapUserTrackingMode = .follow

    @State var manager = CLLocationManager()

    @StateObject var managerDelegate = locationDelegate()

    var body: some View {
        VStack{
//            Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $tracking)

            Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $tracking, annotationItems: managerDelegate.pins) { pin in
                MapPin(coordinate: pin.location.coordinate, tint: .red)

            }.edgesIgnoringSafeArea(.all)
        }.onAppear{
            manager.delegate = managerDelegate
        }
    }
}

class locationDelegate: NSObject,ObservableObject,CLLocationManagerDelegate{

    @Published var pins : [Pin] = []

    // Checking authorization status...

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {

        if manager.authorizationStatus == .authorizedWhenInUse{
            print("Authorized")
            manager.startUpdatingLocation()
        } else {
            print("not authorized")
            manager.requestWhenInUseAuthorization()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        pins.append(Pin(location:locations.last!))
    }
}

// Map pins for update
struct Pin : Identifiable {
    var id = UUID().uuidString
    var location : CLLocation
}

3      

You will need to ask the user's permission to get their location. To get that, you'll need a plist entry with the key "Privacy - Location When In Use Usage Description" and a string that explains why the app needs that information.

You will also need a method that gets the user's location, and which updates the region accordingly. That can probably go inside your locationDelegate struct.

I think there are several other wires crossed, so to speak. But I'm not sure how to fix them with a single reply. For example, you'll need to tell CLLocationManager that locationDelegate is the delegate. You have correctly added the CLLocationManagerDelegate in the class's declaration, but that is not enough. I am also not sure that the view should own all those location manager properties, but if they do, they need to be @StateObjects at the very least, not @State.

I would approach this screen using a different style, so consider my advice with that caveat. I'm still learning as well.

3      

@bobdel

Thanks for the anwser, I was able to update my code a bit so the map now always is centered around the current location of the user. But im not sure what this you'll need to tell CLLocationManager that locationDelegate is the delegate means nor how i should do it. Would you care to show me?

I tried this @StateObject var manager = CLLocationManager() instead of using @state as you said, bun when doing so i get this error Generic struct 'StateObject' requires that 'CLLocationManager' conform to 'ObservableObject'. Any ideas?

This is my current code:


import SwiftUI
import MapKit
import CoreLocation

struct ContentView: View {

    var body: some View {
        Home()
    }
}

struct Home: View{

    @State var tracking : MapUserTrackingMode = .follow

    @State var manager = CLLocationManager()

    @StateObject var managerDelegate = locationDelegate()

    var body: some View {
        VStack{

            Map(coordinateRegion: $managerDelegate.region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $tracking, annotationItems: managerDelegate.pins) { pin in
                MapPin(coordinate: pin.location.coordinate, tint: .red)

            }.edgesIgnoringSafeArea(.all)
        }.onAppear{
            manager.delegate = managerDelegate
        }
    }
}

class locationDelegate: NSObject,ObservableObject,CLLocationManagerDelegate{
    @Published var pins : [Pin] = []

    // From here and down is new
    @Published var location: CLLocation?

    @State var hasSetRegion = false

    @Published var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 38.898150, longitude: -77.034340),
        span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
    )

    // Checking authorization status...

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {

        if manager.authorizationStatus == .authorizedWhenInUse{
            print("Authorized")
            manager.startUpdatingLocation()
        } else {
            print("not authorized")
            manager.requestWhenInUseAuthorization()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // pins.append(Pin(location:locations.last!))

        // From here and down is new
        if let location = locations.last {

            self.location = location

            if hasSetRegion == false{
                region = MKCoordinateRegion(center: location.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001))
                hasSetRegion = true
            }
        }
    }
}

// Map pins for update
struct Pin : Identifiable {
    var id = UUID().uuidString
    var location : CLLocation
}

3      

im not sure what this you'll need to tell CLLocationManager that locationDelegate is the delegate means nor how i should do it. Would you care to show me?

Wow sorry that was unclear, and its something you've taken care of already with this line of code:

            manager.delegate = managerDelegate

That line is three symbols, manager, delegate, and managerDelegate. The manager is an instance of CLLocationManager, delegate is a property inside manager, and managerDelegate is an instance of the class you have doing most of the work (locationDelegate).

I tried this @StateObject var manager = CLLocationManager() instead of using @state as you said, bun when doing so i get this error Generic struct 'StateObject' requires that 'CLLocationManager' conform to 'ObservableObject'. Any ideas?

CLLocationManager does not conform to the ObservableObject protocol. It does not need to be owned by a SwiftUI object at all. A better approach I think would be to create it inside your locationDelegate class.

In locationDelegate, add these two lines of code:

var manager = CLLocationManager()
manager.delegate = self

This detangles CLLocationManager from your SwiftUI code somewhat.

Is this part of a 100DaysSwiftUI project?

Bob

4      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.