Discover how Core Location Monitor can help you better understand location and beacon events in your app. Learn how to use Core Location Conditions to describe and track the state of events in your app, and find out how you can better respond to transitions in your apps through Swift semantics and improved reliability.
♪ ♪ Nivash: Hello, everyone. I’m Nivash Raaja Karukankaatur Murugasamy, an Engineer in Core Location Frameworks team. Welcome to my talk on Core Location Monitor. I’m really excited to talk about our new CLMonitor API. It enables you to write simple yet powerful monitoring logic with just few lines of code in Swift. You just create a monitor, add conditions, and await events. There it is. Hello, CLMonitor! Let’s go into the details of CLMonitor API. I will begin with an overview of the CLMonitor API, our new simple yet powerful way to monitor for user’s location or beacons. Then, I’ll tell you about the types of conditions you can monitor and how you can add or remove them. Then, I will go over monitoring records, what they contain, and how you can access them during your app’s lifecycle. Followed by the steps you will need to take in your app if any of the monitored conditions encounter an event. And finally, I’ll wrap up with some requirements and recommendations that will help you when using CLMonitor. First, let’s take a look at what a monitor is and how you can create one for monitoring.
CLMonitor is a top level Swift actor and each CLMonitor instance acts as a gateway to monitoring. As it is an actor, it relieves you from the overhead of thread and task synchronization. So accessing the contents of CLMonitor or adding or removing a condition at any time just needs to be awaited. To create a monitor, you call the monitor’s init method with an alpha-numeric string. If there is no prior monitor existing with that name, a new one will be created. Otherwise, the existing one will be opened. Either way, the monitor instance will be returned. Note that only one instance with a given name can be open at any moment. The entity that is being monitored is called a condition. You can add a condition to a CLMonitor instance for monitoring and associate it with an identifier using the add method. The identifier is an alphanumeric string. For instance, "Work" in this example uniquely identifies the record of a condition that is satisfied when the user is at work. The record object and its contents will be accessible by this identifier and the condition will be monitored until it is removed. You can remove the condition from monitoring by calling remove with the same identifier. Removing a condition removes the corresponding record as well. Now that you know how to create a monitor instance and how it relates to conditions, let’s take a look at the types of conditions available, and how you can create one and add it for monitoring. There are two types of conditions supported in iOS. First, CircularGeographicCondition. A circular geographic condition is defined by a center and a radius. The center defines the geographical location of the condition. The radius defines the area under which the condition will be considered satisfied. Anywhere outside of that area, we will report the condition as unsatisfied. This is similar to CLCircularRegion in iOS 16 and prior releases. You define a center by constructing a CLLocationCoordinate2D with the latitude and longitude of interest. Then you create the CircularGeographicCondition with that center and the radius of interest. The other type of condition supported in iOS is BeaconIdentityCondition. BeaconIdentityCondition is similar to CLBeaconIdentityConstraint or CLBeaconRegion that you may have used in iOS 16 and earlier releases. If your company has multiple sites at different locations, you can deploy beacons to detect in your app if the user is at any one of your sites, or at a specific site, or even at a specific section of a specific site.
Consider Apple’s cafeterias as a simple example, available in offices at multiple sites. Let's see how beacons could be deployed effectively to enable location based behavior in an associated app. And as we go, I will talk about how that app can monitor for these beacons by using different flavors of BeaconIdentityCondition under different situations. Let's take a brief look at what defines a beacon. It contains a UUID string, a major number, and a minor number. With BeaconIdentityCondition, you can monitor for a specific beacon by specifying all three: UUID, major, and minor. Or you can wildcard to match any single beacon from a group of beacons by specifying just UUID and major, or just UUID alone. When you leave the minor or the major and minor unspecified, a beacon with any values for those properties could match. Let see how we can use this in our example. We could deploy beacons at these cafeteria sites with all having the same UUID. And, in the app, a BeaconIdentityCondition could be created to monitor for this UUID. Then, if a user nears one of these beacons, the condition will become satisfied. Otherwise, it will be determined as unsatisfied. In code, you can do this by calling the init method that specifies only the UUID. Now that we have deployed beacons at the required locations, you might be interested to detect if a user is at one of the specific sites. In this case, let's see how to monitor whether a user is at the Apple Park site. To achieve this, the beacons deployed at each site will have to share a unique major number. Then, in your app, you can monitor for a BeaconIdentityCondition with that major and the overall UUID. The condition’s state will be determined as satisfied only when the device is at that site where the beacons match both UUID and major values. It will remain unsatisfied at other sites. In code, you can create a BeaconIdentityCondition by calling the init method that specifies only UUID and major.
Now you know how to monitor for all sites or a specific site. But you can also monitor for a specific section within a specific site as well. This can be achieved by deploying beacons at different locations-- say, cuisine stations-- within each site with corresponding minor values. In your app, you can monitor for a BeaconIdentityCondition for a specific minor value along with UUID and major. Such a condition would become satisfied only when the beacon with that minor as well as the overall UUID and major is detected. In code, this means creating a BeaconIdentityCondition by passing in the UUID, major, and minor. When creating a BeaconIdentityCondition, use the init method that suits your need. OK, now that you know how to create different types and flavors of condition, let’s take a look at how you can add them for monitoring.
You can add a condition for monitoring by calling the add method with an alphanumeric string called identifier on your CLMonitor instance. The condition would be associated with the identifier. If a condition is already being monitored with the given identifier, it will be replaced with the new condition that is passed in. When you add a condition, it's initial state will be unknown until determined by Core Location. There might be instances where you are aware of the current state of the condition before you add it. In those cases, you can override the default initial state by passing the state when adding for monitoring. In this example, let’s assume that your app deduces that you’re not at Apple Park site and so expects the condition to be unsatisfied. You can add "assuming: .unsatisfied" to your call. Then, monitoring will begin with state set to unsatisfied. But not to worry. If your assumption of the state is wrong, Core Location will give you the correct state once it is determined. To remove a condition from monitoring, you can call the remove method with the identifier that was passed when the condition was added. Now you know what a condition is, what types are supported, and how to add or remove one from monitoring. Let’s take a detailed look at the contents of a record and how you can get a record or all the records in a monitor to inspect at any time. If you can recall from the earlier slide, when you add a condition for monitoring, Core Location creates a record and adds the condition in that record. In addition to the condition, a record contains another object called event.
The event contains the state representing the current observed state of the condition, whether satisfied, unsatisfied, or unknown, and the date and time at which the condition encountered the state. now you might wonder why there is another instance of condition in the event. This is called the refinement. What is it for? If you can recall from BeaconIdentityCondition, your app can monitor just for UUID , or UUID and major, or UUID along with major and minor. If a condition with major and minor wildcarded becomes satisfied, that event will be delivered with the refinement populated. That refinement condition will carry the UUID but also the major and minor information of the observed beacon. Then, once the condition becomes unsatisfied, the refinement will be reset to nil.
There can be multiple instance of record with each record uniquely addressed by the identifier passed in when the condition was added. All the records of your monitors, associating conditions with lastEvents and identifiers, are stored with your app. This enables you to query conditions and their corresponding states as last observed at any time. Let’s see what that looks like in code.
To retrieve a record associated with a condition, you can call the record method with its identifier. If no condition is being monitored with the identifier you passed, nil will be returned. You can then get the underlying monitored condition by accessing the condition property. And you can get the lastEvent delivered to your app for that condition by accessing the lastEvent property. Then, from the event, you can get the most recently observed state, date, and refinement. Now you know how to retrieve one record. How do you get all the monitoring records? Do you need to keep track of all the identifiers? Well, you don’t have to. We maintain the list for you in the identifiers property on your monitor. You can easily iterate over it to retrieve each record and its contents. Now that you know how to access the contents of a record, let’s see how to consume events as changes occur. The code to receive an event can be implemented using a simple loop wrapped in a Task. When Core Location observes a state for a monitored condition which differs from the state reported in the lastEvent, then Core Location will deliver a new event through the events async sequence property on your monitor which resumes the awaiting loop. The event object delivered brings you the new state and the identifier of the condition affected. Or, while processing the new event, you can also use the identifier to get the record and the lastEvent for that condition. We can use that information to get more context on what has happened now. There it is! Our simple greeter program is complete. Now that you know how CLMonitor works, I have some advice on how to use it best.
Let's begin with three key requirements. First, you can have multiple monitors with different names to separate handling of different conditions, but you must instantiate only one at time for a given name. Because CLMonitor maintains the state of the conditions that it is monitoring, trying to initialize another one with the same name could result in undesired behavior. Second, because events can arrive unpredictably, it's best to always have a Task awaiting on your monitor’s events sequence. An event can only become the lastEvent of some record after you have handled it. So, if a condition changes state while you’re not awaiting events, your monitor will not reflect the new state until you do. Finally, if your app has been terminated, when any monitored conditions encounter an event, Core Location will launch your app in the background as long as it is authorized to receive user location. That means your app needs to reinit monitor and await events whenever it is launched if it remains interested in the state of conditions it is monitoring. One way to do this is by listening to didFinishLaunchingWithOptions app delegate callback.
As the new API results in launching behavior, I strongly recommend using CLMonitor only from your app. Using it in widgets or plug-ins will launch your app instead, and complicate ensuring that only one monitor exists for a given name at a time. Finally, I mentioned this earlier: conditions and their states persist, and events are generated when CLMonitor observes a change of state in one of the conditions it is monitoring, so I strongly recommend looking at those states as CLMonitor represents them rather than maintaining them in your own table then get out of sync with arriving events. That said, some applications, such as SwiftUI visualization, may require a separate representation be kept. If you need to do that, reserve that representation for SwiftUI and don’t use it to reason about expected events. So that’s CLMonitor! I’m really excited about our new API. Try it out! We hope it will greatly improve your monitoring experience. We would love to hear your feedback. We also have a sample app that demonstrates CLMonitor in action. It is available in the resources section of this video. Download it and give it a try. Finally, checkout the session by my colleague Siraj on location updates.
Thanks for watching!
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.