Track Santa with Realm: Part 1

Hello everyone, and happy holidays! We’ve been trying to think up some gifts for all of you brilliant mobile developers. But just what is it that you want? 🤔

To build great apps, of course! 🙌

And the best way for us to help you do that is to build great products. So this December, we’ve got a new tutorial, for those of you who haven’t gotten started with our stuff, and want to become even more productive next year. The best part is, you don’t even have to wait for any particular holiday. It’s starting right now!

This 4-part Swift tutorial will teach you how to make a fairly simple app that tracks Santa’s location as he flies around the world on Christmas Eve. It’s a great introduction to the two products we make: The Realm Mobile Database for data persistence & handling, and the Realm Mobile Platform for syncing. Even if you’ve worked with Realm before, there’s definitely some new information in here for you.

We’ll be building this app:

The finished Santa tracking app

For part 1, we’re going to be doing a lot of setup, then make sure that everything is correct by plotting Santa’s current location on the map. I’ve divided this part into 8 steps, so feel free to do some, then come back to it later.

In future parts, we’ll show his route and update the other data on the screen. One part of this series will be about the Realm Object Server, so definitely keep tuning in to learn more about the newest piece of the platform. With that preview done, let’s get started!

1. Make a new Xcode project.

  1. Use the “Single View Application” template, and name it “Santa Tracker” (or whatever you want, but I’m calling mine “Santa Tracker”).
  2. Be sure to set “Language” to “Swift” and “Devices” to just “iPhone”. We obviously won’t need Core Data, so you can uncheck that box.

2. Set up your storyboard.

  1. This app only has one view controller, so the easiest thing to do is to download the storyboard I’ve prepared for you and copy the view controller from there. You can replace the file Main.storyboard, or copy the vew controller from within Interface Builder.
  2. If you copy within IB, you’ll need to set the view controller as “Is Initial View Controller” in the right-hand panel. You’ll know you’re good to go when you see an arrow coming out of nowhere and pointing into the left side of you view controller.
  3. Feel free to make changes! If you’ve got design ideas, who am I to stop you? 🎨 (But I’m not here to debug Auto Layout, so my use layout if you’re not comfortable with that.)
  4. The two images at the bottom won’t load until you add the right images to your project. You can grab the images here. Just add them to your Assets.xcassets file and then set the image views’ images to the right things. You can figure out which one goes where.
  5. If you copied my storyboard, you’ll need to change your view controller’s class name. Open the file ViewController.swift and change the class name to SantaTrackerViewController. You can also change the file name if you like, but it’s not required. (But I did because then they match and that’s always nice.)
  1. As you may already know if you’ve worked with maps, forgetting to link MapKit (like I did) will crash your app on launch. So embarrassing!
  2. Open your project settings, go to the “General” tab (it’s probably the one you land on), and scroll down to the “Linked Frameworks and Libraries” section all the way at the bottom. Use that to add MapKit.framework.

4. Add Realm to your project.

  1. We’ll be adding Realm Swift using CocoaPods. If you don’t have CocoaPods installed yet, take a minute to install it. If you want to use another installation method, you can see how here, but CocoaPods is the easiest.
  2. Now close that Xcode window! We’ll be making a new workspace that we’ll need to use.
  3. Open Terminal (or your favorite substitute) and navigate to your project directory. This is the folder that contains the .xcodeproj file.
  4. Create a Podfile by executing pod init, then open it and add pod 'RealmSwift', '2.1.0' under the line that says # Pods for Santa Tracker. Save and close.
  5. Back in Terminal, call pod install.
    • If this is your first time using CocoaPods, this can take a while. Get some tea. 🍵
    • If you see a bunch of red text about being Unable to satisfy the following requirements, your Podspec repo is out of date and you need to update it with pod repo update first (which will also take a while 🍵).
    • When you see Pod installation complete!, you’re done!
  6. As CocoaPods notes, from now on you’ll need to open the .xcworkspace file, NOT the .xcodeproj file, or you won’t have access to any of your Pods.

5. Run the app (just to make sure everything is good so far).

  1. Open the .xcworkspace file, and hit Run (a.k.a., play button). ▶️
  2. If the simulator opens to your app, hooray! Everything is set up correctly! 🎉
  3. If it doesn’t run, now’s the time to debug that. Make sure you did all of the steps above. Read through once just to make sure! Let me know on Twitter if you need help, but I have faith in you to debug a little.

6. Set up outlets.

Set up the following outlets in SantaTrackerViewController:

@IBOutlet private weak var timeRemainingLabel: UILabel!
@IBOutlet private weak var mapView: MKMapView!
@IBOutlet private weak var activityLabel: UILabel!
@IBOutlet private weak var temperatureLabel: UILabel!
@IBOutlet private weak var presentsRemainingLabel: UILabel!

You can figure out what goes where! (It’s the labels where the text will change [mostly numbers], and the one map.)

You’ll also need to import MapKit at the top for the MKMapView.

7. Set up basic data models.

A word before we get started with our model classes: You must, I repeat absolutely must, name your properties and models exactly what I name them. If you don’t, your app will crash when it tries to connect to the Realm Object Server in a later part of the tutorial. Then you will be sad.

Don’t be sad. Name your properties the way I do.

  1. Let’s start our data model for Santa. We’ll build on this as we go along, so let’s start with just what we need to show a location. Make a new blank Swift file for Santa called, of course, Santa.swift. We’ll have just one Santa (duh) object ever, but we’ll still need a class so that data can be synced.

    class Santa: Object {
        dynamic var currentLocation: Location?
    }

    (Don’t forget to import RealmSwift in all of these model files.) So we have acurrentLocation property that will tell us where Santa is at the current time, which is of type Location?. “What’s a Location?”, I hear you (and the compiler) asking.

  2. Make another Swift file for that, called Location.swift.

    class Location: Object {
        dynamic var latitude: Double = 0.0
        dynamic var longitude: Double = 0.0
    
        convenience init(latitude: Double, longitude: Double) {
            self.init()
            self.latitude = latitude
            self.longitude = longitude
        }
    }

    This will represent a location in a sync-able format. It’s pretty simple, just a latitude and longitude, plus a convenience initializer for later.

  3. So that should make Santa.swift compile, but let’s add one convenience to Santa before we go. Notice how currentLocation is optional? I don’t want to deal with unwrapping all over the place, especially when I know of a great default location for Santa. Amend Santa.swift to look like this:

    class Santa: Object {
        private dynamic var _currentLocation: Location?
        var currentLocation: Location {
            get {
            	// If we don't know where Santa is, he's probably still at home
            	// I hear GPS isn't great up there
                return _currentLocation ?? Location(latitude: 90, longitude: 180)
            }
            set {
                _currentLocation = newValue
            }
        }
           
        override static func ignoredProperties() -> [String] {
            return ["currentLocation"]
        }
    }

    The API for Santa hasn’t changed at all: still just one currentLocation property, but now it’s non-optional!👏 Per the Realm Swift docs, Object properties (i.e., other Realm objects) “must be optional”, so we can’t provide a default value and make it non-optional in the standard Swift way. Instead, we’ve made the true variable private, and made our public variable behave the way we want. Then to make sure Realm doesn’t think it needs to persist this, we’ll tell Realm to ignore it. (Realm ignores read-only properties, but we’ve added a setter so that doesn’t help us.)

  4. As a convenience to ourselves while testing, let’s make some demo data:

    extension Santa {
        static func test() -> Santa {
            let santa = Santa()
            santa.currentLocation = Location(latitude: 37.7749, longitude: -122.4194)
            return santa
        }
    }

    I made mine say Santa is in San Francisco, but you can point him to your city if you get the coordinates.

    Sidebar: Don’t mix up latitude and longitude, as I did many (many [many]) times while writing this tutorial. Here’s a helpful diagram! If the pin isn’t showing up where you want it to, check that first.

8. Show Santa’s location on the map!

  1. Since the map is a defined piece of functionality, let’s make a class to handle all of that. Make a new Swift file called MapManager.swift:

    class MapManager: NSObject {
        private let santaAnnotation = MKPointAnnotation()
    
        init(mapView: MKMapView) {
            santaAnnotation.title = "🎅"
            super.init()
            mapView.addAnnotation(self.santaAnnotation)
        }
    
        func update(with santa: Santa) {
            // Update the map to show Santa's new location
        }
    }

    You’ll need to import MapKit so the compiler knows about the map classes. The MapManager will be in charge of an MKPointAnnotation, which represents a single point on a map and will draw itself using the familiar pin UI. When we create a MapManager, we’ll create the annotation and add it to the map.

  2. So how do we tell the pin where to go, and how do we update it? It’s not the MapManager’s responsibility to know when it needs to update, so someone will tell us when to do that, and give us a Santa to use. All we have to do then is update the annotation’s coordinate property to the new location.

    But we have some problems here. Santa’s location is a Location, but MKPointAnnotation expects a CLLocationCoordinate2D. And a more subtle bug: If update(with:) gets called on a thread other than the main thread, this will crash, because changing the location causes calls to UIKit. (This threading issue won’t bite us this time, but it will later in this series if we aren’t thoughtful now.)

    First things first: Converting Locations to CLLocationCoordinate2Ds is pretty easy. Let’s add an extension to Location to let us do that:

    private extension Location {
        var clLocationCoordinate2D: CLLocationCoordinate2D {
            return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        }
    }

    I’ve named it in the style of UIColor.cgColor, and all it does is copy over the two numbers. Easy! Be sure to add it in MapManager.swift, since that’s the only place where that extension is needed, and it means you don’t have to import CoreLocation in Location.swift.

    The threading problem is also easy to fix: Just do the coordinate change inside Dispatch.main.async.

    Let’s put those together to write update(with:):

    func update(with santa: Santa) {
        let santaLocation = santa.currentLocation.clLocationCoordinate2D
        DispatchQueue.main.async {
            self.santaAnnotation.coordinate = santaLocation
        }
    }

    Since we can’t access the Santa on a thread other than the one is was created on (per the docs), we can’t pass it directly onto the main thread. So we just grab the data we need and pass it over.

  3. Alright, so now we can tell the manager to put Santa the map, so let’s do it! Update SantaTrackerViewController:

    // Has to be implicitly unwrapped
    // Needs the reference to the map view
    private var mapManager: MapManager!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Set up the map manager
        mapManager = MapManager(mapView: mapView)
    
        // Find the Santa data in Realm
        let realm = try! Realm()
        let santas = realm.objects(Santa.self)
    
        // Set up the test Santa if he's not already there
        if santas.isEmpty {
            try? realm.write {
                realm.add(Santa.test())
            }
        }
    
        // Be responsible in unwrapping!
        if let santa = santas.first {
            // Update the map
            mapManager.update(with: santa)
        }
    }

    Since we’re working with our Realm models now, you’ll need to import RealmSwift at the top of the file. So here we set up the map manager, which unfortunately has to be implicitly unwrapped, becuase it needs the reference to the map view. (I know this could be architected differently for now to remove this problem, but we’ll need this in a later part of this tutorial and it’s easier to minimize refactoring since I have psychic knowledge of what the future will hold. 🔮)

    Then we open Realm, set up the test data if it’s not already set up, and pass the first (and only) Santa along to update our map. We’ll improve the error handling around Realm in later parts, so don’t worry about it being a little clunky for now.

    Some things that might cause crashes at this point (at least the ones I ran into):

    • Have you imported the right frameworks in the right files?
    • Did you connect all the @IBOutlets?
    • Do you have all the code from above? Especially if you retyped things, it’s easy to miss things.

    Once you get your app to run and a pin appears where you wanted it to go, congratulations! You’re done (with part 1)! Get some eggnog (or whatever festive holiday treat you like) and I’ll see you back here next week, when we’ll plot Santa’s entire route.


Check out part 2, available now! Get it wherever gifts are sold!


Michael Helmbrecht

Michael Helmbrecht

Michael designs and builds things: apps, websites, jigsaw puzzles. He's strongest where disciplines meet, and is excited to bring odd ideas to the table. But mostly he's happy to exchange knowledge and ideas with people. Find him at your local meetup or ice cream shop, and trade puns.