swiftswiftuiasync-awaitcore-locationibeacon

Handling async functions in Swift


I'm trying to implement CLMonitor to monitor my iBeacons.

@MainActor class BeaconHandler: ObservableObject {
    static let shared = BeaconHandler()
    
    private let locationManager: CLLocationManager
    private var monitor: CLMonitor?
    
    private init() {
        self.locationManager = CLLocationManager()
        Task(priority: .high) {
            self.monitor = await CLMonitor("BeaconHandler")
        }
    }
    
    func addToMonitor(identifier: String, major: Int, minor: Int) {
        Task {
            let beaconCondition = CLMonitor.BeaconIdentityCondition(uuid: UUID(uuidString: identifier)!, major: UInt16(major), minor: UInt16(minor))
            
            await self.monitor?.add(beaconCondition, identifier: identifier, assuming: .unsatisfied)
        }
    }
    
    func removeFromMonitor(identifier: String) {
        Task {
            await self.monitor?.remove(identifier)
        }
    }
    
    func getEventStateFor(identifier: String) async -> CLMonitor.Event.State {
        if self.monitor == nil {
            return .unmonitored
        }
        
        if let monitoredRecord = await self.monitor!.record(for: identifier) {
            return monitoredRecord.lastEvent.state
        }
        
        return .unmonitored
    }
}

What I want to achieve is to add beacons to the monitor, and get current state of its beacon every second to display using SwiftUI. But, I am not sure if it's the best apporach to initialise CLMonitor, since addToMonitor gets called before the initialisation of CLMonitor.

How can I fix this?


Solution

  • For me it seems you are trying to "pull" the state of the Beacon. I would advise against that. The CLMonitor has an async sequence called events that can be set up to "observe" changes to the state of the beacon.

    According to this wwdc video the latest state will only become populated with the latest value if you observe the change. So you should start observing as soon as your application starts.

    A more general example of how this would work:

    // add the async Stream observer
    do{
        for try await event in await monitor.events{
            if let monitoredRecord = await self.monitor.record(for: identifier) {
                let lastEvent = monitoredRecord.lastEvent
                // manipulate the model according to the last Event
            }
        }
    } catch{
        
    }