[wp-svg-icons icon=”location-2″ wrap=”h1″] iOS allows each application to monitor up to 20 regions. That seems like a lot, especially when you figure that the regions are registered and can be triggered (entry and exit) even while your app is backgrounded or your phone is on standby. So what do you do when you have more than 20? iOS will simply ignore them if added to your locationManager.
You can monitor the 20 closest (most likely) to be encountered. Why bother registering for regions that are far away? So in theory upon significant user location change, triggering a region, etc.
- stop monitoring for all regions that may exist for your location manager
- find the 20 closest regions from the user’s location
- register for those 20
Rinse and repeat based on your own logic (as mentioned before as significant user location update, the addition or subtraction of a region, etc.)
For me personally, I wanted to figure out how to do this myself. I know I needed to perform a sort based on distances.
I had an array of CLCircleRegions.
var monitoredRegions:[CLCircularRegion] = []
I pushed into that when placing my pin annotations on the map. You can’t sort them though. Hmm. I needed something with a property I could sort. So I created a struct with a region and distance.
struct SortableRegion { var distance: Double var region: CLCircularRegion }
Cool. Getting closer. We can sort on that distance property. Here is a function that basically does what I need it to do. I’ll walk through it after showing it to you.
func checkAndMonitorTwentyClosestRegions() { stopMonitoringAllRegions() if monitoredRegions.count > 20 { print("we need to only monitor the 20 closest regions. Call this anytime regions are added, removed, or the user moves significantly.") var sortableRegions:[SortableRegion] = [] let location: CLLocation = CLLocation(latitude: myMapView.userLocation.coordinate.latitude, longitude: myMapView.userLocation.coordinate.longitude) for region in self.monitoredRegions { let fenceLocation = CLLocation(latitude: region.center.latitude, longitude: region.center.longitude) let distance = location.distance(from: fenceLocation) let sortRegion = SortableRegion(distance: distance, region: region) sortableRegions.append(sortRegion) } let sortedArray = sortableRegions.sorted { $0.distance < $1.distance } // Grab the first 20 closest and monitor those. let firstTwentyRegions = sortedArray[0..<20] for item in firstTwentyRegions { let region = item.region locationManager.startMonitoring(for: region) } } } else { print("Less than 20 regions, monitor them all.") for region in monitoredRegions { locationManager.startMonitoring(for: region) } } }
Right off the bat, I call a method that stops monitoring for all regions a location manager might have. Basically just:
for monitored in locationManager.monitoredRegions { locationManager.stopMonitoring(for: monitored) }
Now I check how many regions there are. If there are less than 20, we register them all. Easy. If there are more than 20, we need to find the nearest 20 of them and start monitoring those.
I created an empty array typed to SortableRegion (that struct I made). I then grabbed a reference to the user’s location. We want to use that to determine the distance to the center of every region. I create an instance of SortableRegion and append that to the array. For each region. So each instance now has reference to the CLCircleRegion as well as the distance. Perfect. We then sort the whole thing when we’re done based on distance, lowest to highest.
Then we make an instance of the sorted array (using a range) – grabbing just the first 20 items. Pretty nifty. We loop through that array and using the reference to CLCircleRegion, monitor for each one. When you want to check again, make sure the monitoredRegions is properly updated first, and then call the checkAndMonitorTwentyClosestRegions function and you should be all set.