Sending an unlimited amount of location triggered notifications

How we worked around Apple’s limit of 64 local pending notifications to build the HERE for Local Journalism app.

Ajay Chainani
The Lenfest Local Lab @ The Inquirer

--

Image by Tumisu used under CC BY

We recently launched HERE for Local Journalism — an app that tests an idea that people want local news alerts as they approach the place a story was written about. The hypothesis is that that these notifications will be highly relevant and engaging.

We decided to test the idea by building an application that does this on the iOS platform because Apple provides the UserNotifications framework, which in theory makes it easy to create location-aware alerts. Here is an example of code to trigger a location-aware notification:

// Define the content of the notification
let content = UNMutableNotificationContent()
content.title = place.title
content.body = place.blurb
content.sound = UNNotificationSound.default()

// Define the region
let region = CLCircularRegion(center: place.coordinate(), radius: place.radius ?? 100, identifier: place.identifier)
region.notifyOnEntry = true
region.notifyOnExit = false

// Define the trigger
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)

// Define the request
let request = UNNotificationRequest(identifier: place.identifier, content: content, trigger: trigger)

// Add the request
let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { (error) in
if let error = error {
print("\n\t ERROR: \(error)")
} else {
print("\n\t request fulfilled \(request)")
}
})

However, we soon learned that we needed to modify our approach because Apple caps the number of client-side pending notifications at just 64. We knew that our app could eventually support a much larger number of places tagged with local news stories, so we came up with a novel solution that utilized the background location monitoring capabilities of the iPhone — and these were the steps:

  1. Monitor the users’ location for significant location changes
  2. Get the users’ current location
  3. Fetch the 10 closest places or pins to that location
  4. Remove all previously registered pending local notifications
  5. Register the 10 new region monitoring local notifications
  6. Get a significant location change update (500 meters or more)
  7. Repeat steps 2, 3, 4, 5 & 6

The following is a snippet of the relevant code:

// MARK: - Significant location change montioring logic

// Called at launch of application if permission is already granted
func startMonitoringSignificantLocationChanges() {
locationManager.startMonitoringSignificantLocationChanges()
}

// Called by location manager if there is a significant location change
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let latestLocation = locations.last else {
// No location found
return
}
self.latestLocation = latestLocation
self.updateTrackedPlaces(coordinate: latestLocation.coordinate)
}

func updateTrackedPlaces(coordinate: CLLocationCoordinate2D) {
// Fetches new places
dataStore.retrievePlaces(latitude: coordinate.latitude, longitude: coordinate.longitude) { [unowned self] (success, data, count) in
if self.authorized {
// Track the new places
self.trackPlaces(places: data)
}
}
}

func trackPlaces(places: [Place]) {
// Removes all the currently monitored regions
self.removeAllMonitoredRegions()
// Saves the new places in our place data store
PlaceManager.shared.savePlaces(places: places)
for place in places {
// Starts monitoring the new places
self.locationManager.startMonitoring(for: place.region())
}
}

// MARK: - Region montioring logic

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
guard let region = region as? CLCircularRegion else {
// region type was not valid
return
}

enteredRegion(region)
}

func enteredRegion(_ region: CLCircularRegion) {
if !canTriggerNotification(for: region) {
return
}

let identifier = region.identifier
// Retrieve place from data store
guard let place = PlaceManager.shared.placeForIdentifier(identifier) else {
// Could not find place with that identifier
return
}

triggerNotificationForPlace(place)
}

This solution allowed our app to support more than the 64 notifications Apple permits. Our users can now move about the city and receive notifications as if their phone was monitoring all of the regions in our database.

We are committed to transparency and helping local journalism at the lab. Hence, if you are curious to see all our code you can find our application’s code repository here.

The Lenfest Local Lab is a small, multidisciplinary product and user experience innovation team located in Philadelphia, PA supported by The Lenfest Institute for Journalism.

The Lenfest Institute for Journalism is a non-profit organization whose mission is to develop and support sustainable business models for great local journalism. The Institute was founded in 2016 by entrepreneur H.F. (Gerry) Lenfest with the goal of helping transform the news industry in the digital age to ensure high-quality local journalism remains a cornerstone of democracy.

--

--

Ajay Chainani
The Lenfest Local Lab @ The Inquirer

Japan + NYC. iOS developer. Startup advisory. Previously @spring @techstars @500startups, @sonar, @loudieapp. 日本語 OK! http://ajay.jp