civil-and-structural-engineering
Implementing Location-based Reminders with User Notifications in Ios
Table of Contents
Introduction to Location-Based Reminders in iOS
Location-based reminders transform a standard mobile application into a context-aware assistant that delivers timely notifications precisely when a user enters or leaves a defined geographic area. From reminding a user to pick up a prescription when near a pharmacy to notifying them to set a home alarm when leaving the house, this feature enhances daily productivity and engagement. In iOS, the combination of the Core Location framework and the UserNotifications framework provides a robust and privacy‑conscious foundation for implementing such functionality. This article provides a comprehensive, production‑ready guide to building location‑based reminders, covering everything from permissions and geofence configuration to advanced optimisations and testing strategies.
Understanding Core Location and User Notifications
The two primary iOS frameworks involved are Core Location and UserNotifications. Core Location handles all geographic tracking, including region monitoring (geofences), significant‑change location services, and standard GPS position updates. UserNotifications manages the scheduling, presentation, and management of local and remote notifications. For location‑based reminders, we use local notifications because they are triggered entirely on the device, require no network connection, and respect the user’s privacy.
Region monitoring with CLCircularRegion is the most straightforward approach. You define a circular area (centre coordinate and radius) and instruct Core Location to notify your app when the device crosses the boundary. To receive those events while the app is in the background or terminated, you must request “always” location authorization (not just “when in use”). Always‑authorization is governed by strict App Store guidelines and must be justified in the app’s Info.plist strings and UI.
Setting Up Permissions
Requesting Location Authorization
Before any location monitoring can begin, your app must obtain the user’s explicit permission. iOS displays a system prompt that includes the purpose string from your Info.plist. For background region monitoring, you need NSLocationAlwaysAndWhenInUseUsageDescription (iOS 11+) or NSLocationAlwaysUsageDescription. The code should first call requestAlwaysAuthorization() on the CLLocationManager instance:
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
Apple encourages developers to defer the permission request until the user has context. Do not request always‑authorization on first launch; instead, explain the benefit before showing the prompt. Also handle the case where the user initially denies permission by guiding them to the Settings app.
Requesting Notification Authorization
Simultaneously, you must request permission to display notifications. This is done via UNUserNotificationCenter:
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
// Handle response
}
If the user denies notification permission, consider offering an alternative (e.g., in‑app alerts) or explaining how notifications enhance the experience. Both permissions are independent – a user might allow location but disable notifications, or vice versa.
Configuring Geofences and Region Monitoring
Defining a Circular Region
A geofence is implemented using CLCircularRegion. You supply a centre coordinate (latitude and longitude) and a radius in meters. The minimum radius iOS reliably supports is about 100 meters, though testing on devices with good GPS can yield smaller radii. Specify whether you want to be notified on entry, exit, or both:
let center = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let region = CLCircularRegion(center: center, radius: 150, identifier: "OfficeRegion")
region.notifyOnEntry = true
region.notifyOnExit = true
locationManager.startMonitoring(for: region)
The identifier string must be unique per region; it is used to match incoming events. iOS supports a maximum of 20 simultaneously monitored regions per app. If you need more, consider combining regions or using significant‑change location to trigger a more granular check.
Monitoring Multiple Regions
Often you will have many reminders (e.g., user‑created geofences). Store region identifiers in a local database (Core Data, Realm, or UserDefaults). When the app launches, re‑register all active regions. Also remove any regions that are no longer needed to stay within the 20‑region limit.
// Remove regions when no longer needed
locationManager.stopMonitoring(for: region)
Monitor the monitoringDidFail(for:withError:) delegate method to catch cases where region monitoring is not supported (e.g., on older devices) or is restricted by system settings.
Handling Region Events
When a device crosses a geofence boundary, Core Location calls one of two delegate methods: locationManager(_:didEnterRegion:) or locationManager(_:didExitRegion:). Your implementation should verify the region identifier and then schedule a local notification. Example:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
guard region.identifier == "OfficeRegion" else { return }
scheduleNotification(title: "Welcome to Office", body: "Don't forget to scan your badge.")
}
Note that these delegate methods are called even if your app is suspended; iOS will wake your app in the background briefly. You have about 30 seconds to execute code before the system suspends you again. Use that time to schedule the notification (or handle other logic) as quickly as possible.
Handling State Restoration After App Restart
After the app is terminated (e.g., user force‑quits or the system reclaims memory), regions are still monitored by the system. However, when an event occurs and the app is not running, iOS relaunches your app into the background and delivers the same delegate message. You must ensure your app’s initialisation code correctly configures the CLLocationManager delegate and re‑establishes any necessary state. Store enough information in persistent storage to handle the event without user interaction.
Sending Local Notifications
Creating the Notification Content
Use UNMutableNotificationContent to define the title, body, sound, and custom data:
let content = UNMutableNotificationContent()
content.title = "Reminder"
content.body = "You're near the store! Don't forget to buy groceries."
content.sound = .default
content.userInfo = ["regionID": "StoreRegion"]
You may also include a categoryIdentifier to enable custom actions (e.g., “Snooze” or “Mark as Done”).
Trigger and Request
Because we want the notification to fire immediately after the region event, we use a time‑interval trigger with a very short delay (1 second). Repeating should be false:
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
// Log error (e.g., notification not added)
}
}
Using a unique identifier each time prevents overwriting previous notifications. If you want to replace a pending notification (e.g., update the message), use the same identifier.
Customizing Sound and Badge
You can provide a custom sound file in your bundle (must be less than 30 seconds) by setting content.sound = UNNotificationSound(named: UNNotificationSoundName("my_sound.caf")). Badge numbers are set via content.badge = 1 (or increment based on UIApplication.shared.applicationIconBadgeNumber). Remember that the badge is cleared only when the user opens the app or you manually reset it.
Error Handling and Battery Considerations
Common Errors
Region monitoring can fail for several reasons:
- Not supported on device – some older iPads or iPod touches lack a cellular/GPS chip. Check
CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self). - Location services disabled –
CLLocationManager.locationServicesEnabled()may return false. - Authorization denied –
authorizationStatusis.deniedor.restricted. - Exceeded region limit – if you try to monitor more than 20 regions,
startMonitoring(for:)will fail.
Implement the locationManager(_:monitoringDidFail:with:) delegate to log the error and inform the user gracefully (e.g., “Unable to monitor this location”).
Battery Optimisation
Location monitoring is power‑intensive. The system already optimises region monitoring by using cell tower and Wi‑Fi positioning when possible, but you can further reduce impact:
- Monitor only as many regions as needed (keep under 20).
- Use larger radii when precision is not critical (e.g., 500m vs 50m).
- Disable region monitoring when the app enters the background for unrelated tasks (use
stopMonitoring(for:)in appropriate lifecycle callbacks). - For non‑critical reminders, consider using significant‑change location service (
startMonitoringSignificantLocationChanges()) which provides updates only when the device moves significantly (500m or more) and consumes less power.
Testing Location-Based Reminders
Simulating Location in Xcode
Xcode’s Simulator allows you to simulate location changes. Use the “Simulate Location” feature (Debug > Simulate Location) and choose a GPX file or a freeway drive. You can also manually set coordinates via the “Location” button in the debug bar. However, note that background region monitoring does not work in the simulator for all scenarios; it is recommended to test on a physical device.
Writing Unit Tests for Region Logic
Isolate the delegate methods and notification scheduling into testable components. Mock the CLLocationManager and UNUserNotificationCenter using protocols. Example:
protocol RegionHandler {
func didEnterRegion(with identifier: String)
}
class MockNotificationCenter: UNUserNotificationCenter {
var addedRequest: UNNotificationRequest?
override func add(_ request: UNNotificationRequest, withCompletionHandler: (Error?) -> Void) {
addedRequest = request
}
}
Test that entering a region produces the correct notification title and body, and that errors are handled.
Real-World Testing Checklist
- Test entry and exit events while app is in foreground, background, and terminated.
- Verify that multiple overlapping regions trigger independent notifications.
- Check battery drain over several hours of monitoring.
- Test with location services disabled, Wi‑Fi off, and cellular data off.
- Ensure notification prompts are not repeated if the user re‑enters the same region within a short time – implement a “cool‑down” period using UserDefaults timestamps.
Privacy and User Experience Best Practices
Transparency and Consent
Users are increasingly sensitive to location tracking. Always request the minimum level of access needed. For most location‑based reminders, “always” authorization is necessary for background monitoring, but you can still explain why. Provide an in‑app screen before the system prompt that describes the feature and how location data is used (e.g., “We only monitor your location to send you reminders when you enter or leave designated places. Your location data is never uploaded to our servers.”).
Allowing Users to Manage Reminders
Offer a UI where users can view all active reminders, see the geofenced area on a map, and delete reminders individually. Also let them temporarily pause all reminders (e.g., while on vacation). Deleting a reminder should immediately call stopMonitoring(for:).
Feedback and Empty States
When no reminders are set, show a clear empty state with a call‑to‑action (“Tap + to add a location reminder”). If permissions are denied, display a message explaining what the user is missing and a button linking to Settings.
Do Not Disturb and Critical Alerts
Location‑based notifications should not be obtrusive. Respect the user’s current Focus mode (Do Not Disturb) – the system automatically suppresses notifications when enabled. Do not request “critical alert” permission unless the reminder is truly urgent (e.g., medication) and approved by Apple.
Advanced Enhancements
iBeacon-Based Triggers
For indoor precision, consider using iBeacon ranging (CLBeaconRegion) instead of geofences. Beacons provide accuracy down to a few meters. The setup is similar: define a beacon region by UUID, major, and minor values, then monitor for entry/exit and also range for proximity. iBeacon monitoring does not require always‑authorization (when in use is enough) but battery impact is comparable.
Combining with Significant‑Change Location
Significant‑change location updates are triggered by cell tower changes about every 500 meters. You can use them to refresh the list of nearby geofences even when the device is moving fast (e.g., in a car). However, entry/exit events are then computed by your own logic rather than by the system’s dedicated region monitoring. This is useful if you need to monitor hundreds of regions – you can keep a subset in memory based on the user’s current area.
Using Background Tasks for Pre‑fetching
If your reminders require network data (e.g., store opening hours), you can schedule a BGTask (introduced in iOS 13) to refresh data opportunistically. But be careful not to combine it with location events to avoid appearing to track the user in the background for non‑location purposes.
Handling Multiple Notifications from the Same Region
If a user lingers on a region boundary, they might receive repeated entry/exit events. Implement a cooldown timer (e.g., “Do not trigger another notification for this region for at least 5 minutes”). Store the last trigger timestamp in UserDefaults per region identifier.
Conclusion
Implementing location‑based reminders in iOS is a powerful way to deliver context‑aware experiences. By combining Core Location’s region monitoring with UserNotifications, you can create features that feel intuitive and helpful. The key to success lies in respecting user privacy, optimising battery usage, handling errors gracefully, and providing a clean interface for managing reminders. As iOS continues to evolve, new capabilities such as CLMonitor (iOS 17+) further simplify background monitoring. Build your implementation on the foundation described here, and you will deliver a production‑ready, reliable location‑based reminder system.
For further reading, consult the official Apple documentation: