iAchieved.it

Software Development Tips and Tricks

By

CoreLocation on iOS 8 with Swift

Editor’s Note: This is part two in a three part series. The goal in this series is to develop a fully functional (and useful) iOS application that is capable of pushing weather alerts based on your location to your iOS device. The series is broken down as follows:

  • Part One – Developing a Push Notification-capable application with Parse and Swift
  • Part Two – Using CoreLocation with Swift
  • Part Three – Integrating in a Weather Service API

In this second part of our three part series we’ll be expanding on our weather alerts application started in Part One. A warning! The code examples that follow build upon the code in Part 1.

CoreLocation

CoreLocation is the iOS framework which provides location-based information to your application. By location we mean either the phone’s location in a GPS-coordinate space, or we can mean in reference to beacon devices. In this tutorial we are interested in the GPS location of the device.

Before we get started using CoreLocation, it is important to note that Apple has raised the bar in terms of how resource-intensive your application is with regards to using location-based services. Our application, Lewis and Clark, made use of CoreLocation to send frequent and accurate GPS locations to determine whether a user was in a new county or state. Apple had approved the application through several iterations and then rejected the application on the grounds it was requesting “navigation-level” GPS coordinates and was not a navigation application. When writing an application that makes use of location updating strive to only request the bare minimum accuracy necessary for your application to work correctly.

Okay, let’s get started. In your weatheralerts Xcode project, create a new Swift file called CoreLocationController.swift. If you don’t have the project you can get the version from here. Make sure and read the Part One post to obtain API keys for Parse and add them to your project.

CoreLocation uses the delegation pattern, so our class needs to declare that is implements the CLLocationManagerDelegate protocol. If you are unfamiliar with using the delegation pattern in iOS, see the following excellent tutorials:

Back to the code! Here is our initial content for CoreLocationController.swift:

Next let’s declare a member variable locationManager and configure it in the init() method as follows (this code goes in your CoreLocationManager class!):

Note: To declare Swift classes as delegates you must first inherit from NSObject. See Stackoverflow for a good answer as to why.

Before we forget, go to the AppDelegate and add:

as a member variable after the pushNotificationController declaration, and in the didFinishLaunchingWithOptions function add:

after the self.pushNotificationController = PushNotificationController() line.

Delegate Methods

There are a number of CoreLocation delegate methods we need to implement in our CoreLocationController class, but the first we will implement is locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus). iOS 8 introduced changes to how apps are authorized to use CoreLocation, and we want to capture when our app moves from a given authorization state to the Authorized state. To capture that transition we implement the delegate method:

If we tried to compile and run this code as is you would notice that no authorization request is made. iOS 8 requires you to not only request authorization status (with the code requestAlwaysAuthorization function), but also provide a description as to why you are requesting a given authorization. The text description is given by the key NSLocationAlwaysUsageDescription (when requesting always authorization, which is used to receive location updates while the app is in the background) in your Info.plist. To add it, edit your Info.plist in Xcode. I prefer to right-click in the Info.plist editor window and Show Raw Keys/Values before inserting a new key.

In this example we’ve added our key NSLocationAlwaysUsageDescription and the reason we give is “Your location is used to send you timely weather alerts in your immediate area.”

tut_locationalways_usage
Show Raw Keys/Values

Run the application again and you should see:

tut_access_location

Press Allow and your application should switch to an .Authorized status. If you have the same println statements in your code as above you will see in the console log:

Notice that when our app changed to .Authorized we call self.locationManager.startUpdating(). startUpdating() is a method on CLLocationManager which will be responsible for obtaining GPS coordinates and delivering them to us. And since this is a delegate pattern, we need to implement the function that will be called when CLLocationManager has GPS coordinates: locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!).

Here’s our initial implementation of this delegate function:

Run the application again and you should begin receiving a stream of locations. Too many locations as a matter of fact, and they are all likely right on the same location. We want to fine tune how many location updates we receive, as well as our relax our accuracy requirements.

Consider our use-case, receiving weather alerts relative to our location. Does it matter if our GPS location is accurate down to a meter? No. Chances are a weather alert in your area is going to be the same as in your next-door neighbor’s area, i.e., you are in the same “area”. We can greatly reduce the demands of our application without sacrificing accuracy by relaxing our GPS accuracy to a kilometer. Moreover, we don’t need any additional updates to our location unless we move significantly, say 3 kilometers. This can be achieved by adding the following code after self.locationManager.delegate = self in our init() routine.

An illustration:
tut_accuracy_distance

The location reported by our phone is the green dot. Because our accuracy is set to one kilometer we could actually be anywhere in the blue circle. Again, we don’t really care. On the edge of the blue circle, right in the center, etc., its all the same for a weather alert. The red circle represents our distance filter, that is, the distance we have to move before we will receive another update. As long as our green dot stays within the red circle, we won’t receive an update from CoreLocation, which again is fine for our purposes.

Note: In our application we might even be better served by significant location change updates from iOS, but for our purposes we’ll use standard location updating.

Reverse Geocoding

We now have the basics for receiving location updates whenever our phone moves approximately 3 kilometers from its previous location, and our location is accurate within a kilometer. Our weather API will require us to pass city, state, and country information to provide weather alerts. We can obtain this information by using the reverse geocoding feature of the CLGeocoder class. Reverse geocoding relies on databases to take GPS coordinates and return the “address” of that location. iOS encapsulates the returned data in a CLPlacemark object which has various attributes we can extract as follows (this code goes in our didUpdateLocations delegate function):

We’ll note that the call reverseGeocodeLocation returns immediately, and it is only after the reverse geocoding results are available (a network request is made to Apple servers to look up the data) is the completionHandler block called. Heed the warning in Apple’s documentation when using reverse geocoding! Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value kCLErrorNetwork to your completion handler.

Simulating Movement

At some point we’re going to need to test our application and ensure that current weather alerts are recognized and displayed to the user. I live in North Texas, and while we do get severe weather from time to time (tornados anyone?), we don’t have severe weather every day. So how are we going to test our application? Simple. We will take a look at Wunderground’s severe weather alert map, pick a city within an alert area, look up its GPS coordinates, and give that information to Xcode to simulate for us.

Before we go into the details of getting the GPS address of a city of interest, let’s start with creating a GPX file in Xcode for GPS-location simulation. To create a GPX file use Xcode File – New – File and select iOS Resource – GPX File

tut_create_gpx_file

Name the file Waypoints. Edit the file in Xcode and delete the default waypoint (the lone <wpt/> element) and then add:

This coordinate is in Buffalo, New York, which, as of this writing, has a winter weather alert. Run the application and then use Xcode to simulate the location based upon the content of the Waypoints GPX file. This is done within Xcode, on the bar above the Debug Area notice the location arrow:

tut_simulate_waypoints

You should see after triggering the location simulator to use Waypoints:

Posting Our Location

I am a big fan of the notification model in iOS application development. Rather than add hooks across classes with references to objects, or create a new delegate protocol, simply post a notification to the notification center and let anyone who is interested in the data subscribe to it (sometimes referred to as pub-sub).

We will eventually write a class that handles taking our location and uses a weather service API to look up whether there are any active alerts. That class is going to listen for an event that the CoreLocationController will publish. For simplicity we’ll call it the LOCATION_AVAILABLE event. Here’s how we use the notification center to post it. After extracting the CLPlacemark data into a userInfo dictionary, add the following line:

Let’s go ahead and create the class that will receive and handle the notification. Create a new file called WeatherServiceController and implement the following:

Note: WeatherServiceController must inherit from NSObject to make use of the notification center. Failure to derive the class from NSObject will result in the following type of trap when the notification is posted by the CoreLocationController:

Before trying to run the again, remember, we have to create an instance of our WeatherServiceController. Head back to the AppDelegate and add:

to your member variable declarations and:

in your didFinishLaunchingWithOptions function. At this point your AppDelegate should look a bit something like this:

Running the code again and simulating to Buffalo, New York:

Receiving Events in the Background

If you’ve been working along and put the application in the background, you’ll notice no updates are received! That sort of defeats the purpose, so we have to explicitly add the capability to receive location updates in the background. Go to your application target and navigate to the Capabilities page and scroll down to Background Modes. Turn background modes on and select Location updates

tut_background_location_updates

Bitbucket Xcode Project

The work done thus far is available on Bitbucket on a branch named part2. To use the project you can directly download the zip file and open the enclosed Xcode project. There are a few steps you’ll need to complete to use the project:

  • Sign up for Parse and obtain your own API keys
  • Change the Bundle Identifier to reflect your organization (that is, change it.iachieved. to com.yourcompany)
  • Create and add your own ApiKeys.plist file and use your Parse API keys
  • Create and configure your own provisioning profile

Part 3

In part three we’ll be adding the final touches to the application which will be passing the reverse geocoded location to our weather service API. This API will in turn provide us with information about weather alerts for the area, and if any are available we will provide that information to Parse to receive a push notification.

Leave a Reply

Your email address will not be published. Required fields are marked *