ios Map & Location APIs Maps & Location Tech tutorial

Core Location Tutorial for iOS: Tracking Visited Locations

Core Location Tutorial for iOS: Tracking Visited Locations

Core Location Tutorial for iOS: Tracking Visited Locations

Introduction

iOS has very highly effective APIs for monitoring consumer places and dealing with maps. You is perhaps making an app for your native café and need to notify the consumer of particular gives when they’re close by. Or, you’re making a music participant and also you need to begin enjoying some bouncy beats when the consumer enters a health club. Tracking location on a regular basis, nevertheless, can drain a battery actually quick! On this Core Location tutorial, you’ll discover ways to use a few of the lesser-known, however very highly effective, capabilities of the framework. You’ll discover ways to create an app that logs consumer actions through the day but in addition doesn’t drain a lot of a tool’s battery.

On this tutorial, you’ll make a journey logging app. Think about you’re touring to a brand new metropolis. As an alternative of preserving a journal, this app will mechanically monitor places you go to, so you possibly can keep in mind them later and examine them with your mates.

Right here’s what this app will do:

  • Monitor the consumer’s location, even when the app just isn’t open.
  • When the app logs a brand new location, ship the consumer an area notification.
  • Save these places in a file.
  • Show an inventory of logged places.
  • Show a map with consumer location and logged places.
  • Permit customers to manually log their present location.

Core Location visited locations tutorial finished app

With out additional ado, it’s time to get into it.

Getting Began

First, obtain the undertaking supplies utilizing the Obtain Supplies hyperlink on the prime or backside of this tutorial. Open the starter challenge. Proper now, the app doesn’t do a lot. On the primary tab, you have got an empty listing with places. On the second tab, you’ll see an empty map view.

Core Location tutorial starter app

Right here’s what’s within the starter venture:

AppDelegate.swift

You will have two further imports on the prime of the file:


import CoreLocation
import UserNotifications

The CoreLocation framework listens to consumer location updates. You’ll use the UserNotifications framework to point out banner notifications when the app logs a brand new location.

You may also see two additional properties within the class declaration:


let middle = UNUserNotificationCenter.present()
let locationManager = CLLocationManager()

By means of these two properties, you’ll entry the API of the 2 frameworks above.

PlacesTableViewController.swift

That is the primary tab of the app — the UITableViewController subclass — with an empty datasource implementation. In the event you want a greater understanding of any of the strategies, take a look at this superior course on desk views earlier than you proceed.

MapViewController.swift

That is the view controller for the second tab. You will have the MKMapView attached from the storyboard. You even have an IBAction for the plus button within the navigation bar.

Location.swift

That is the mannequin class. It has 5 saved properties:


let latitude: Double
let longitude: Double
let date: Date
let dateString: String
let description: String

  • latitude and longitude are what you’d anticipate them to be: the coordinates of the situation.
  • The date is the precise date when this location was logged.
  • dateString is a human readable model of the date.
  • description is a human readable handle of the situation.

There’s additionally a computed property to transform the Location object to CLLocationCoordinate2D, which can be helpful later within the tutorial.

There are two initializers for this class; you’ll study them later on this tutorial.

You should save objects of this class on disk. With a purpose to do this, this class conforms to Codable. Codable is a Swift four language function that lets you encode and decode objects simply. For extra info on this function, take a look at our tutorial on Codable.

LocationsStorage.swift

Final however not least, with this singleton, you’ll save places to the paperwork folder of the app.

This class has a few properties to do the writing and studying from the disk:


personal let fileManager: FileManager
personal let documentsURL: URL

It additionally has a property to entry all logged places, which, for now, is about to an empty array within the initializer:


personal(set) var places: [Location]

Now that you simply’re in control with the preliminary code, it’s time so as to add some new code!

Core Location: Asking for Consumer Locations

Step one is to ask permission to trace the consumer’s location. Within the age of privateness scandals, Apple stands fairly robust on holding customers answerable for which knowledge an app can collect. That’s why it is rather necessary to correctly ask customers to permit the app entry to collect the required knowledge.

Offering a Correct Description

To collect location modifications knowledge, you must set two particular strings within the Information.plist file:

Setting up Info.plist for Core Location

The app presents these strings when it asks for permission. Be happy to vary the prompts to any textual content you want so long as the textual content fulfills the next necessities:

  • Encourage customers to provide the entry.
  • Let customers know precisely how, and for what cause, the info is being collected.
  • The assertion is 100% true.

Asking for Locations Permissions

Open AppDelegate.swift and add this line earlier than the return assertion in software(_:didFinishLaunchingWithOptions:):


locationManager.requestAlwaysAuthorization()

With this line, you ask customers to permit the app to entry location knowledge each within the background and the foreground.

Construct and run the challenge. It is best to get a dialog just like this one, however it’ll embrace the strings you set within the Information.plist file earlier within the tutorial:

Core Location permission alert

Faucet All the time Permit.

Observe: The consumer might prohibit the entry to location knowledge for background or for each; on this case, it’s the developer’s duty to gracefully deal with these instances correctly. To maintain issues easy for this tutorial, you’ll assume that the consumer selects All the time Permit.

Asking for Notifications Permissions

Location permissions aren’t the one permissions you want: To point out a consumer notifications, it’s essential to ask permission.

To take action, you don’t need to specify any further strings. Simply add this code proper above the road you simply added:


middle.requestAuthorization(choices: [.alert, .sound]) granted, error in

Right here, you move choices to specify what sort of notifications you need to publish. You additionally embrace an empty closure since you assume, for this tutorial, that customers all the time offer you permission. You possibly can deal with the denial on this closure.

Construct and run. You need to see this dialog:

Notifications permission alert

Faucet Permit.

Selecting the Most Applicable Locations Knowledge

The Core Location framework has some ways to trace a consumer’s location and every has totally different traits:

  • Normal location providers: Excessive battery influence. Excessive location precision. Nice for navigational or health apps.
  • Vital location modifications: Medium battery impression. Medium location precision. Low stops precision.
  • Regional monitoring: Low battery influence. Nice location precision. Requires particular areas to be able to monitor.

None of those is absolutely appropriate for your app. Low battery influence is a should — a consumer is unlikely to make use of the app in any other case. What’s extra, regional monitoring can also be undesirable since you restrict consumer motion to some particular areas.

Luckily, there’s one different API you need to use.

Go to Monitoring

Go to monitoring lets you monitor locations — locations the place the consumer stops for some time. It wakes the app each time a brand new go to is detected and could be very power environment friendly and never tied to any landmark.

Subscribe to Location Modifications

Now that you already know which of the various Core Location APIs you’ll use to get the consumer’s location, it’s time to start out implementing it!

CLLocationManager

Within the AppDelegate.swift, under this line:


locationManager.requestAlwaysAuthorization()

add the next code:


locationManager.startMonitoringVisits()
locationManager.delegate = self

The primary line initiates the listening function. Core Location makes use of delegate callbacks to tell you of location modifications.

Now, add this extension on the backside of the file:


extension AppDelegate: CLLocationManagerDelegate
func locationManager(_ supervisor: CLLocationManager, didVisit go to: CLVisit)
// create CLLocation from the coordinates of CLVisit
let clLocation = CLLocation(latitude: go to.coordinate.latitude, longitude: go to.coordinate.longitude)

// Get location description


func newVisitReceived(_ go to: CLVisit, description: String)
let location = Location(go to: go to, descriptionString: description)

// Save location to disk

The primary technique is the callback from CLLocationManager when the brand new go to is recorded and it supplies you with a CLVisit.

CLVisit has 4 properties:

  1. arrivalDate: The date when the go to started.
  2. departureDate: The date when the go to ended.
  3. coordinate: The middle of the area that the gadget is visiting.
  4. horizontalAccuracy: An estimate of the radius (in meters) of the area.

You might want to create a Location object from this knowledge and, in the event you recall, there’s an initializer that takes the CLVisit, date and outline string:


init(_ location: CLLocationCoordinate2D, date: Date, descriptionString: String)

The one factor lacking within the above is descriptionString.

Location Description

To get the outline, you’ll use CLGeocoder. Geocoding is the method of changing coordinates into addresses or place names in the actual world. If you wish to get an handle from a set of coordinates, you employ reverse geocoding. Fortunately, Core Location provides us a CLGeocoder class which does this for us!

Nonetheless in AppDelegate.swift, add this property on the prime of the category:


static let geoCoder = CLGeocoder()

Now, on the backside of locationManager(_:didVisit:), add the next code:


AppDelegate.geoCoder.reverseGeocodeLocation(clLocation) placemarks, _ in
if let place = placemarks?.first
let description = “(place)”
self.newVisitReceived(go to, description: description)

Right here, you ask geoCoder to get placemarks from the situation. The placemarks include a bunch of helpful details about the coordinates, together with their addresses. You then create an outline string out of the primary placemark. After you have the outline string, you name newVisitReceived(_:description:).

Sending Native Notifications

Now, it’s time to inform a consumer when the brand new go to location is logged. On the backside of newVisitReceived(_:description:), add the next:


// 1
let content material = UNMutableNotificationContent()
content material.title = “New Journal entry ?”
content material.physique = location.description
content material.sound = .default

// 2
let set off = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: location.dateString, content material: content material, set off: set off)

// three
middle.add(request, withCompletionHandler: nil)

With the above, you:

  1. Create notification content material.
  2. Create a one second lengthy set off and notification request with that set off.
  3. Schedule the notification by including the request to notification middle.

Construct and run the app. At this level, the app is usable in that it logs visits and notifies the consumer.

In case you are utilizing an actual gadget and have a while for a stroll, you possibly can check your work proper now. Go some place and cease to have a espresso. Visits require you stay at a spot for some time period. It is best to obtain some notifications, like this:

The visits are being recorded, however the visits are usually not but endured.

Faking Knowledge (Non-compulsory)

Strolling is sweet for your physique, nevertheless it could be an issue to do it proper now in the midst of constructing this app! To check the app with out truly strolling, you need to use the Route.gpx file. This type of file lets you simulate the system or simulator GPS location. This specific file will simulate a stroll round Apple’s campus in Cupertino.

To make use of it, within the Debug space, click on the “Simulate Location” icon, after which choose “Route” from the listing:

You possibly can open the tab with a map or Maps app to see the strolling route.

Faking CLVisits

iOS data CLVisits behind the scenes, and typically you may wait for as much as 30 minutes with a purpose to get the callback! To keep away from this, you’ll have to implement mechanics that pretend CLVisit recording. You’ll create CLVisit situations, and since CLVisit has no accessible initializer, you’ll have to make a subclass.

Add this to the top of AppDelegate.swift:


remaining class FakeVisit: CLVisit
personal let myCoordinates: CLLocationCoordinate2D
personal let myArrivalDate: Date
personal let myDepartureDate: Date

override var coordinate: CLLocationCoordinate2D
return myCoordinates


override var arrivalDate: Date
return myArrivalDate


override var departureDate: Date
return myDepartureDate


init(coordinates: CLLocationCoordinate2D, arrivalDate: Date, departureDate: Date)
myCoordinates = coordinates
myArrivalDate = arrivalDate
myDepartureDate = departureDate
tremendous.init()


required init?(coder aDecoder: NSCoder)
fatalError(“init(coder:) has not been implemented”)

With this subclass, you possibly can present preliminary values for CLVisit‘s properties.

Set Up locationManager

Now you want the locationManager to inform you when the situation modifications. For this, on the finish of software(_:didFinishLaunchingWithOptions:), earlier than return assertion, add the next:


// 1
locationManager.distanceFilter = 35

// 2
locationManager.allowsBackgroundLocationUpdates = true

// three
locationManager.startUpdatingLocation()

Right here’s what these strains do:

  1. Obtain location updates when location modifications for n meters and extra.
  2. Permit location monitoring in background.
  3. Begin listening.

You possibly can remark out these three strains to show off the visits faking.

Deal with Pretend Visits

It’s time to deal with the situation callback. For this, add the next code to AppDelegate‘s CLLocationManagerDelegate extension:


func locationManager(_ supervisor: CLLocationManager, didUpdateLocations places: [CLLocation])
// 1
guard let location = places.first else
return


// 2
AppDelegate.geoCoder.reverseGeocodeLocation(location) placemarks, _ in
if let place = placemarks?.first
// three
let description = “Fake visit: (place)”

//four
let fakeVisit = FakeVisit(
coordinates: location.coordinate,
arrivalDate: Date(),
departureDate: Date())
self.newVisitReceived(fakeVisit, description: description)


  1. Discard all places besides for the primary one.
  2. Seize the situation description, as you probably did earlier than.
  3. Mark the go to as a pretend one.
  4. Create a FakeVisit occasion and move it to newVisitReceived perform.

Construct and run the app. Activate the Route location simulation. Shut the app or lock your iPhone and it is best to get a brand new notification round as soon as per minute.

Persisting Location Knowledge

To save lots of the visited places, you’ll use Swift’s Codable protocol to encode the visited places into JSON and write that to a file.

Saving Data on Disk

Open LocationsStorage.swift. On the backside of the category, add the next perform:


func saveLocationOnDisk(_ location: Location)
// 1
let encoder = JSONEncoder()
let timestamp = location.date.timeIntervalSince1970

// 2
let fileURL = documentsURL.appendingPathComponent(“(timestamp)”)

// three
let knowledge = attempt! encoder.encode(location)

// four
attempt! knowledge.write(to: fileURL)

// 5
places.append(location)

Right here’s what you do with that code:

  1. Create the encoder.
  2. Get the URL to file; for the file identify, you employ a date timestamp.
  3. Convert the situation object to uncooked knowledge.
  4. Write knowledge to the file.
  5. Add the saved location to the native array.

Word: For simplicity, this tutorial merely assumes the JSON conversion and the writing the file will succeed. You must all the time implement a correct error dealing with technique in your manufacturing code.

Now, open AppDelegate.swift and inside newVisitReceived(_:description:), proper beneath this:


let location = Location(go to: go to, descriptionString: description)

add this:


LocationsStorage.shared.saveLocationOnDisk(location)

Now, every time the app receives a go to, it’s going to seize the situation description, create a Location object and reserve it to disk.

To check this, you might want to do the next two issues:

  1. Permit the consumer to log his or her present location.
  2. Show all saved data in a UITableViewController.

Saving a Present Location

To save lots of the present location, open MapViewController.swift. Inside addItemPressed(_:), add this code:


guard let currentLocation = mapView.userLocation.location else
return


LocationsStorage.shared.saveCLLocationToDisk(currentLocation)

As you possibly can see, there isn’t any saveCLLocationToDisk(_:) but, so open LocationsStorage.swift and add this code to the underside of the category:


func saveCLLocationToDisk(_ clLocation: CLLocation)
let currentDate = Date()
AppDelegate.geoCoder.reverseGeocodeLocation(clLocation) placemarks, _ in
if let place = placemarks?.first
let location = Location(clLocation.coordinate, date: currentDate, descriptionString: “(place)”)
self.saveLocationOnDisk(location)


Right here, you create a Location object from clLocation, the present date and the situation description from geoCoder. You save this location the identical means as you probably did earlier than.

Now, inside the initializer, substitute this line:


self.places = []

with this:


let jsonDecoder = JSONDecoder()

// 1
let locationFilesURLs = attempt! fileManager
.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil)
places = locationFilesURLs.compactMap url -> Location? in
// 2
guard !url.absoluteString.incorporates(“.DS_Store”) else
return nil

// three
guard let knowledge = attempt? Knowledge(contentsOf: url) else
return nil

// four
return attempt? jsonDecoder.decode(Location.self, from: knowledge)
// 5
.sorted(by: $zero.date < $1.date )

With this code, you:

  1. Get URLs for all information within the Paperwork folder.
  2. Skip the .DS_Store file.
  3. Learn the info from the file.
  4. Decode the uncooked knowledge into Location objects — thanks Codable ?.
  5. Type places by date.

With this code, when the app launches, LocationsStorage could have places taken from disk.

Establishing the App to Use Saved Knowledge

Establishing a Desk View

Sufficient with the boring behind-the-scenes stuff!

Now, it is time for you to see the outcomes.

For this, open PlacesTableViewController.swift. Substitute tableView(_:cellForRowAt:) with the next implementation:


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: “PlaceCell”, for: indexPath)
let location = LocationsStorage.shared.places[indexPath.row]
cell.textLabel?.numberOfLines = three
cell.textLabel?.textual content = location.description
cell.detailTextLabel?.textual content = location.dateString
return cell

Construct and run the app. You will see one thing just like this:

Core Location visited locations list

If you have no recorded visits, attempt saving them manually on the second tab and construct run the app once more. As you possibly can see, with a view to replace the record, it is advisable to re-run the app. You will repair this subsequent.

Updating the Record When a Location is Logged

To maintain the listing up to date, it’s essential to publish a notification for the app to know that a new location was recorded. Please observe, nevertheless, that this isn’t UNNotification, however a Notification. This notification is for the app’s inner utilization, not for notifying customers.

Open LocationsStorage.swift. On the backside of the file, add this extension:


extension Notification.Identify
static let newLocationSaved = Notification.Identify(“newLocationSaved”)

That is the notification that you’ll publish.

Now, on the finish of saveLocationOnDisk(_:), add this code:


NotificationCenter.default.publish(identify: .newLocationSaved, object: self, userInfo: [“location”: location])

Subsequent, you must take heed to this notification in PlacesTableViewController.

Navigate to PlacesTableViewController.swift, and add the next code on the prime of the category:


override func viewDidLoad()
tremendous.viewDidLoad()

// 1
NotificationCenter.default.addObserver(
self,
selector: #selector(newLocationAdded(_:)),
identify: .newLocationSaved,
object: nil)


// 2
@objc func newLocationAdded(_ notification: Notification)
// three
tableView.reloadData()

With the above code, you:

  1. Register a way to be referred to as when your notification arrives.
  2. Obtain the notification as a parameter.
  3. Reload the listing.

Construct and run the app. To save lots of your present location, faucet the plus button on the second tab. Opening the primary tab, you must see that variety of places has elevated:

Establishing MapView With All Logged Locations

The ultimate a part of this tutorial will present you how you can show places on a map with pins.

So as to add pins to the map, you must convert places to MKAnnotation, which is a protocol that represents objects on a map.

Open MapViewController.swift and add this technique to the top of the category:


func annotationForLocation(_ location: Location) -> MKAnnotation
let annotation = MKPointAnnotation()
annotation.title = location.dateString
annotation.coordinate = location.coordinates
return annotation

This creates a pin annotation with a title and coordinates.

Now, on the finish of viewDidLoad(), add this:


let annotations = LocationsStorage.shared.places.map annotationForLocation($zero)
mapView.addAnnotations(annotations)

This code generates pins from places that you’ve got already created and provides them to the map.

Construct and run the app. It is best to see pins on the map, like this:

show existing pins

The one factor left to do is so as to add a pin when a brand new location is logged.

To do that, add the next perform on the finish of MapViewController:


@objc func newLocationAdded(_ notification: Notification)
guard let location = notification.userInfo?[“location”] as? Location else
return


let annotation = annotationForLocation(location)
mapView.addAnnotation(annotation)

Equally to the primary tab, it is advisable pay attention for the notification with a view to know when this new location is recorded.

To subscribe to this notification, add this code to the top of viewDidLoad:


NotificationCenter.default.addObserver(
self,
selector: #selector(newLocationAdded(_:)),
identify: .newLocationSaved,
object: nil)

Construct and run the app. Log a brand new location on the second tab. A pin ought to seem on the map:

dynamically add a pin

And that is your journey logger finished! Be happy to stroll round city and see what locations it data! This function lets you see and react to the place the consumer goes with out draining an excessive amount of battery. Simply remember that the info you are amassing is delicate knowledge, so gather it responsibly.

The place to Go From Right here?

You possibly can obtain the finished model of the venture utilizing the Obtain Supplies button on the prime or backside of this tutorial.

To deepen your understanding on this matter, take a look at these helpful assets:

As all the time, when you’ve got any questions or feedback, be happy to submit within the dialogue discussion board under. :]

Obtain Supplies

(perform(d, s, id)
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = “//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5&appId=118196468516511”;
fjs.parentNode.insertBefore(js, fjs);
(doc, ‘script’, ‘facebook-jssdk’));