CLMonitor Add region after starting to monitor for event changes

I am currently working on an app that uses the CLMonitor to check when the user has entered a specific region. When the user enters the region, a new region should be monitored, and the old region should be removed.

Currently, I have a startMonitoring() method that contains the event handling logic:

func startMonitoringConditions() {
        Task {
            monitor = await CLMonitor(MonitorNames.monitorName)
            
            if let identifiers = await monitor?.identifiers {
                if identifiers.count == 0 {
                    if let coordinate = manager.location?.coordinate {
                        await addNewRegionAtCoordinate(coordinate: coordinate)
                    }
                }
                else {
                    print("Previous Monitor Region is used.")
                }
            }

            for try await event in await monitor!.events {
                if let coordinate = manager.location?.coordinate {
                    // do something...                  
                    await monitor!.remove(event.identifier)
                    await addNewRegionAtCoordinate(coordinate: coordinate)                    
                }
            }
        }
    }

Unfortunately, adding a new region will not update the events collection in the CLMonitor, so the new region's events will not be handled in this method.

Any help on how I could fix this problem would be greatly appreciated!

Answered by selvi in 771151022

I managed to create a workaround that is poor code design but should solve the issue.

func startMonitoringConditions() {
        Task {
            monitor = await CLMonitor(MonitorNames.monitorName)
            
            if let identifiers = await monitor?.identifiers {
                if identifiers.count == 0 {
                    if let coordinate = manager.location?.coordinate {
                        await addNewRegionAtCoordinate(coordinate: coordinate)
                    }
                }
                else {
                    print("Previous Monitor Region is used.")
                }
            }
             
            while continueTrackingRegions {
                for try await event in await monitor!.events {
                    if let coordinate = manager.location?.coordinate {
                        // do something                       
                        await monitor!.remove(event.identifier)
                        await addNewRegionAtCoordinate(coordinate: coordinate)
                        break
                    }
                }
            }
        }
    }

This update seems to work for me although I am unable to fully confirm if it works 100% of the time because I am now running into an issue with the new SwiftData API. Gotta love ModelContexts...

Accepted Answer

I managed to create a workaround that is poor code design but should solve the issue.

func startMonitoringConditions() {
        Task {
            monitor = await CLMonitor(MonitorNames.monitorName)
            
            if let identifiers = await monitor?.identifiers {
                if identifiers.count == 0 {
                    if let coordinate = manager.location?.coordinate {
                        await addNewRegionAtCoordinate(coordinate: coordinate)
                    }
                }
                else {
                    print("Previous Monitor Region is used.")
                }
            }
             
            while continueTrackingRegions {
                for try await event in await monitor!.events {
                    if let coordinate = manager.location?.coordinate {
                        // do something                       
                        await monitor!.remove(event.identifier)
                        await addNewRegionAtCoordinate(coordinate: coordinate)
                        break
                    }
                }
            }
        }
    }

This update seems to work for me although I am unable to fully confirm if it works 100% of the time because I am now running into an issue with the new SwiftData API. Gotta love ModelContexts...

Forgot to give an explanation for how I think this fix works:

My understanding is that CLMonitor.events will only contain events from regions that were added to the CLMonitor before running the for loop. Adding a new region to the CLMonitor inside of the for loop means that the new region's event updates will not appear in the CLMonitor.events collection in this loop. The workaround then is to stop listening for events and restart the for loop.

I'm experiencing the same bug with Xcode Version 15.3 and iPhone Simulator 17.4. When the app is launched monitoring events are received but are not received for any regions added while app already running. I noticed this sample (which is some very ropey SwiftUI by the way) shows the bug too so I will include that in my feedback report as the test harness: https://developer.apple.com/documentation/corelocation/monitoring_location_changes_with_core_location

I experienced the same bug and have reported it as FB13696956

You can find my report on openradar (the link is not allowed here for some reason?). Another issue I noticed is the for try await events does not throw a cancellation exception when the outer task is cancelled like normal streams do.

Please submit your own feedback and reference my report so hopefully it will bring more attention to it so it can be fixed!

You need to start a task that listens for events simultaneously to when you add a new region to monitor, that's the only way to grab the first event at the right time. This can be done with task groups.

CLMonitor Add region after starting to monitor for event changes
 
 
Q