Augmented Reality iOS Tutorial: Location Based

In this augmented reality tutorial, you’ll learn how to use your iOS users location to create compelling augmented reality experiences. By Jean-Pierre Distler.

4.5 (4) · 2 Reviews

Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Loading Points of Interest

Now that you have an API key open PlacesLoader.swift and find the line let apiKey = "Your API key" and replace the value with your API key.

This is a great time for a new test, but before you build and run, open ViewController.swift and add two new properties below the locationManager property.

fileprivate var startedLoadingPOIs = false
fileprivate var places = [Place]()

startedLoadingPOIs tracks if there is a request in progress, it can happen that the CLLocationManagerDelegate method is called multiple times even after you stopped updating the location. To avoid multiple requests you use this flag. places stores the received POIs.

Now find locationManager(manager: didUpdateLocations:). Inside the if statement, add the following code, right after the "More code later ..." comment:

//1
if !startedLoadingPOIs {
  startedLoadingPOIs = true
  //2
  let loader = PlacesLoader()
  loader.loadPOIS(location: location, radius: 1000) { placesDict, error in
  	//3
    if let dict = placesDict {
      print(dict)
    }
  }
}

This starts loading a list of POIs that are within a radius of 1000 meters of the user’s current position, and prints them to the console.

Build and run, and watch the console’s output. It should look like this, but with other POIs:

{
    "html_attributions" =     (
    );
    "next_page_token" = "CpQCAgEAAJWpTe34EHADqMuEIXEUvbWnzJ3fQ0bs1AlHgK2SdpungTLOeK21xMPoi04rkJrdUUFRtFX1niVKCrz49_MLOFqazbOOV0H7qbrtKCrn61Lgm--DTBc_3Nh9UBeL8h-kDig59HmWwj5N-gPeki8KE4dM6EGMdZsY1xEkt0glaLt9ScuRj_w2G8d2tyKMXtm8oheiGFohz4SnB9d36MgKAjjftQBc31pH1SpnyX2wKVInea7ZvbNFj5I8ooFOatXlp3DD9K6ZaxXdJujXJGzm0pqAsrEyuSg3Dnh3UfXPLdY2gpXBLpHCiMPh90-bzYDMX4SOy2cQOk2FYQVR5UUmLtnrRR9ylIaxQH85RmNmusrtEhDhgRxcCZthJHG4ktJk37sGGhSL3YHgptN2UExsnhzABwmP_6L_mg";
    results =     (
                {
            geometry =             {
                location =                 {
                    lat = "50.5145334";
                    lng = "8.3931416";
                };
                viewport =                 {
                    northeast =                     {
                        lat = "50.51476485000001";
                        lng = "8.393168700000002";
                    };
                    southwest =                     {
                        lat = "50.51445624999999";
                        lng = "8.3930603";
                    };
                };
            };
            icon = "https://maps.gstatic.com/mapfiles/place_api/icons/lodging-71.png";
            id = c64c6c1abd02f4764d00a72c4bd504ab6d152a2b;
            name = "Schlo\U00df-Hotel Braunfels";
            photos =             (
                                {
                    height = 4160;
                    "html_attributions" =                     (
                        "<a href=\"https://maps.google.com/maps/contrib/113263673214221090182/photos\">Ralph Peters</a>"
                    );
                    "photo_reference" = "CoQBdwAAABZT7LYlGHmdep61gMOtwpZsYtVeHRWch0PcUZQOuICYHEWnZhKsSkVdMLx3RBTFIz9ymN10osdlqrPcxhxn-vv3iSsg6YyM18A51e3Sy0--jO2u4kCC05zeMyFp-k7C6ygsDsiOK4Dn3gsu_Bf5D-SZt_SrJqkO0Ys6CwTJ75EPEhDcRLUGnYt2tSODqn_XwxKWGhRMrOG9BojlDHFSoktoup1OsbCpkA";
                    width = 3120;
                }
            );
            "place_id" = ChIJdadOzRdPvEcRkItOT1FMzdI;
            rating = "3.8";
            reference = "CmRSAAAAgvVO1e988IpXI7_u0IsRFCD1U1IUoSXlW7KfXvLb0DDtToodrGbiVtGZApSKAahnClm-_o-Nuixca_azt22lrT6VGwlJ1m6P0s2TqHAEmnD2QasXW6dCaDjKxesXCpLmEhAOanf32ZUsfX7JNLfNuuUXGhRrzQg-vvkQ0pGT-iSOczT5dG_7yg";
            scope = GOOGLE;
            types =             (
                lodging,
                "point_of_interest",
                establishment
            );
            vicinity = "Hubertusstra\U00dfe 2, Braunfels";
        },

Pardon my french! :]

If you get NULL back for a response, try increasing the radius to a larger value.

So far, your app can determine a user’s position and load a list of POIs inside the local area. You have a class that can store a place from this list, even if you don't use it at the moment. What’s really missing is the ability to show the POIs on the map!

Displaying Places of Interest

To make an annotation on the mapView, you need another class. So go to File\New\File…, choose the iOS\Swift File and click Next. Name the file PlaceAnnotation.swift and click Create.

Inside PlaceAnnotation.swift replace the contents with the following:

import Foundation
import MapKit

class PlaceAnnotation: NSObject, MKAnnotation {
  let coordinate: CLLocationCoordinate2D
  let title: String?
  
  init(location: CLLocationCoordinate2D, title: String) {
    self.coordinate = location
    self.title = title
    
    super.init()
  }
}

Here you've made the class implement the MKAnnotation protocol and defined two properties and a custom init method.

Now you have everything you need to show some POIs on the map!

Go back to ViewController.swift and complete the locationManager(manager: didUpdateLocations:) method. Find the print(dict) line and replace it with this:

//1
guard let placesArray = dict.object(forKey: "results") as? [NSDictionary]  else { return }
//2
for placeDict in placesArray {
  //3
  let latitude = placeDict.value(forKeyPath: "geometry.location.lat") as! CLLocationDegrees
  let longitude = placeDict.value(forKeyPath: "geometry.location.lng") as! CLLocationDegrees
  let reference = placeDict.object(forKey: "reference") as! String
  let name = placeDict.object(forKey: "name") as! String
  let address = placeDict.object(forKey: "vicinity") as! String
              
  let location = CLLocation(latitude: latitude, longitude: longitude)
  //4
  let place = Place(location: location, reference: reference, name: name, address: address)              
  self.places.append(place)
  //5
  let annotation = PlaceAnnotation(location: place.location!.coordinate, title: place.placeName)
  //6
  DispatchQueue.main.async {
    self.mapView.addAnnotation(annotation)
  }
}

Here's a closer look at what’s happening above:

  1. The guard statement checks that the response has the expected format
  2. This line iterates over the received POIs
  3. These lines get the needed information from the dictionary. The response contains a lot more information that is not needed for this app.
  4. With the extracted information a Place object is created and appended to the places array.
  5. The next line creates a PlaceAnnotation that is used to show an annotation on the map view.
  6. Finally the annotation is added to the map view. Since this manipulates the UI, the code has to be executed on the main thread.

Build and run. This time, some annotations appear on the map and when you tap one, you’ll see the name of the place. This app looks nice for now, but where is the augmented reality?!

Map_Annotations

Introducing HDAugmentedReality

You’ve done a lot of work so far, but they’ve been necessary preparations for what you’re about to do: it’s time to bring augmented reality to the app.

You may have seen the Camera button in the bottom right. Currently nothing happens if you tap the button. In this section you'll add some action to this button and show a live preview of the camera with some augmented reality elements.

To make your life easier you'll use the HDAugmentedReality library. It is already included in the starter project you downloaded earlier, if you want to grab the latest version you can find it on Github, but what can this lib do for you?

First, HDAugmentedReality handles the camera captioning for you so that showing live video is easy. Second, it adds the overlays for the POIs for you and handles their positioning.

As you’ll see in a moment, the last point is perhaps your greatest boon, because it saves you from having to do some complicated math! If you want to know more about the math behind HDAugmentedReality, continue on.

If, on the other hand, you want to dig immediately into the code, feel free to skip the next two sections and jump straight to Start Coding.

Warning, Math Inside!

You're still here, so you want to learn more about the math behind HDAugmentedReality. That’s great! Be warned, however, that it’s a bit more complicated than standard arithmetic. In the following examples, we assume that there are two given points, A and B, that hold the coordinates of a specific point on the earth.

A point’s coordinates consist of two values: longitude and latitude. These are the geographic names for the x- and y-values of a point in the 2D Cartesian system.

  • Longitude specifies if a point is east or west of the reference point in Greenwich, England. The value can be from +180° to -180°.
  • Latitude specifies if a point is north or south of the equator. The range is from 90° at the north pole to -90° at the south pole.

If you have a look at a standard globe, you’ll see lines of longitude that go from pole to pole – these are also known as meridians. You’ll also see lines of latitude that go around the globe that are also called parallels. You can read in geography books that the distance between two parallels is around 111 km, and the distance between two meridians is also around 111km.

There are 360 meridian lines, one for every degree out of 360 degrees, and 180 lines of parallel. With this in mind, you can calculate the distance between two points on the globe with these formulas:

\Delta lon =(A_{lon} - B_{lon}) * 111
\Delta lat =(A_{lat} - B_{lat}) * 111

This gives you the distances for latitude and longitude, which are two sides of a right triangle. Using the Pythagorean theorem, you can now calculate the hypotenuse of the triangle to find the distance between the two points:

That’s quite easy but unfortunately, it's also wrong.

If you look again at your globe, you’ll see that the distance between the parallels is almost equal, but the meridians meet at the poles. So the distance between meridians shrinks when you come closer to the poles, and is zero on the poles. This means the formula above works only for points near the equator. The closer the points are to the poles, the bigger the error becomes.

To calculate the distance more precisely, you can determine the great-circle distance. This is the distance between two points on a sphere and, as we all know, the earth is a sphere. Well OK, it is nearly a sphere, but this method gives you good results. With a known latitude and longitude for two points, you can use the following formula to calculate the great-circle distance.

This formula gives you the distance between two points with an accuracy of around 60 km, which is quite good if you want to know how far Tokyo is from New York. For points closer together, the result will be much better.

Phew - that was hard stuff! The good news is that CLLocation has a method, distanceFromLocation:, that does this calculation for you. HDAugmentedReality also uses this method.