Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Combine - Create publisher for CoreLocation

I have only started learning combine, so it's still a little fuzzy to me. I would like to create a custom Publisher, that would use CLLocationManager to expose current user location. I would like it to work in such a way, that locationManager only starts updating location when there are some subscribers connected. And after all subscribers were removed, canceled, etc, then it should stop updating location. Is this possible to do? How can I create such a publisher? Also is this the right approach, or is there something wrong with it?

like image 339
Damian Dudycz Avatar asked Dec 31 '22 11:12

Damian Dudycz


1 Answers

The basics of what you want are pretty straightforward. There's an example from Using Combine that wraps CoreLocation with a object that acts as a proxy, returning a publisher of CLHeading updates.

CoreLocation itself doesn't automatically start or stop that sort of thing, and in the proxy object I copied that pattern to allow you to manually start and stop the updating process.

The core of the code is in https://github.com/heckj/swiftui-notes/blob/master/UIKit-Combine/LocationHeadingProxy.swift

import Foundation
import Combine
import CoreLocation

final class LocationHeadingProxy: NSObject, CLLocationManagerDelegate {
    let mgr: CLLocationManager
    private let headingPublisher: PassthroughSubject<CLHeading, Error>
    var publisher: AnyPublisher<CLHeading, Error>

    override init() {
        mgr = CLLocationManager()
        headingPublisher = PassthroughSubject<CLHeading, Error>()
        publisher = headingPublisher.eraseToAnyPublisher()

        super.init()
        mgr.delegate = self
    }

    func enable() {
        mgr.startUpdatingHeading()
    }

    func disable() {
        mgr.stopUpdatingHeading()
    }
    // MARK - delegate methods
    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        headingPublisher.send(newHeading)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        headingPublisher.send(completion: Subscribers.Completion.failure(error))
    }
}

This doesn't quite do everything you asked, in that it doesn't automatically start updates upon subscription, but I suspect you could expand this to enable that capability.

So far I haven't delved into making my own publishers by implementing all the methods required by the protocol, so I don't have detail extending to that mechanism. Combine itself has the concept of ConnectablePublisher for when you want explicit control over the updates, although most of the publishers and operators trigger on either creation of the publisher or subscription.

In general, IF you should do so is better answered by your use case. In some instances, you're creating the pipelines and subscribing before you're updating views - if that's the case, then holding off on asking for the background updates would save you some processing power and energy consumption.

In the UIKit examples that use this CoreLocation publisher, I also have the mechanisms in place to verify permissions have been requested to allow for Location updates, embedded within an example view controller: https://github.com/heckj/swiftui-notes/blob/master/UIKit-Combine/HeadingViewController.swift

like image 65
heckj Avatar answered Jan 11 '23 14:01

heckj