Silver Challenge: Displaying the User's Region

This challenge took me a while to figure out mainly because the wording in the challenge and the clues had me believing that it had to display my current -real- location, when in reality, and from what I was able to accomplish, all it does is show the simulator’s location, which can be set by tapping command + shift + ,

The first part of setting the “Privacy - Location When in Use Usage Description” is easy, and can be done by following the steps in chapter 15.

Here are the changes I made along the MapViewController class for the second part:

I made MapViewController conform to the MKMapViewDelegate protocol.

class MapViewController: UIViewController, MKMapViewDelegate {

I created the property to hold an instance of CLLocationManager.

var mapView: MKMapView!
var userLocation: CLLocationManager!

Next, I created the instance of CLLocationManager and called requestWhenInUseAuthorization() to prompt the user to give the app the required permissions. I also assigned the MapViewController to be the mapView’s delegate, so that I could implement the callback to call the setRegion(_:animated:) method on the mapView, when the location had been updated.

Another important thing occurs here, the showUserLocation property in the mapView is set to True, this is what allows the map to show the blue dot annotation on the map.

override func viewDidLoad() {
        mapView.delegate = self
        userLocation = CLLocationManager()
        userLocation.requestWhenInUseAuthorization()
        mapView.showsUserLocation = true
        print("MapViewController loaded its view.")
}

Lastly, I implemented the function mapView(_: didUpdate:) to be called whenever the user’s location is updated on the map, this happens right after giving the app permissions to access the simulator’s location, so it gets called right away. The coordinates of the user’s location can be found in the coordinate property of the userLocation object of type CCLocationManager that was instantiated above. The MKCoordinateSpan is basically setting up the desired zoom level, with lower values representing a higher zoom level. Lastly, I use the setRegion(_:animated:) method to make the map show the desired region. If the animated parameter is set to True it kind of flies to the location, if set to False it just basically shows the spot right away.

func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
        let span = MKCoordinateSpan(latitudeDelta: 0.08, longitudeDelta: 0.08)
        let theRegion = MKCoordinateRegion(center: userLocation.coordinate, span: span)
        mapView.setRegion(theRegion, animated: true)
}

Hope this helps! Looking forward to seeing other possible solutions and maybe someone can answer the question below :slight_smile:

Are there any rules when it comes to the place where you set a delegate programmatically? I did it in viewDidLoad() but it also worked if done in loadView(), so I was wondering what would the preference be and why? Thanks in advance!

2 Likes

I don’t know if there’s a preference between setting the delegate in viewDidLoad or loadView; I chose to set it in loadView. In fact, just about everything you put in viewDidLoad I chose to put in loadView except for the call to requestWhenInUseAuthorization which I put in viewDidAppear. That way it doesn’t ask the user for permission to use their location until the first time they select the map.

The other thing I did different was to only set the span when the map is initially displayed. That way, the user can change the map scale & the program won’t keep resetting it when the user location changes:

func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation)
{
    if !zoomLevelSet
    {
        mapView.region = MKCoordinateRegion(center: userLocation.coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
        zoomLevelSet = true
    }
    else
    {
        mapView.region.center = userLocation.coordinate
    }
}

zoomLevelSet is reset to false in viewDidAppear.

After a bit of reading, it seems that viewDidLoad is probably a better place to do those things than loadView. loadView is only overridden if you’re creating your views programmatically (which we started doing in chapter 5). If you’re using Interface Builder to create your views (as in chapters 1, 3, and 4) then loadView is not overridden (and should not be overridden), and viewDidLoad is your only option.

2 Likes

Only slightly ashamed of using a video on this one :slight_smile: Here it is: iOS MapKit Tutorial - How To Display Location on a Map - YouTube