Skip to content

Instantly share code, notes, and snippets.

@malhal
Last active February 12, 2024 23:19
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save malhal/22f534d47d620216c25d812af9bcc227 to your computer and use it in GitHub Desktop.
Save malhal/22f534d47d620216c25d812af9bcc227 to your computer and use it in GitHub Desktop.
A Combine location publisher for CLLocationManager.
// Requirements: a NSLocationWhenInUseUsageDescription entry in Info.plist
// Usage: @State var locator = CLLocationManager.publishLocation()
// and
// .onReceive(locator) { location in
// Improvements needed: Move requestWhenInUseAuthorization into its own publisher and perhaps have a combineLatest pipeline for both authorized and valid location.
// A configuration param to init(), e.g. so each manager can have the same distanceFilter.
import Foundation
import Combine
import CoreLocation
extension CLLocationManager {
public static func publishLocation() -> LocationPublisher{
return .init()
}
public struct LocationPublisher: Publisher {
public typealias Output = CLLocation
public typealias Failure = Never
public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = LocationSubscription(subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
final class LocationSubscription<S: Subscriber> : NSObject, CLLocationManagerDelegate, Subscription where S.Input == Output, S.Failure == Failure{
var subscriber: S
var locationManager = CLLocationManager()
init(subscriber: S) {
self.subscriber = subscriber
super.init()
locationManager.delegate = self
}
func request(_ demand: Subscribers.Demand) {
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
func cancel() {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
_ = subscriber.receive(location)
}
}
}
}
}
// Tester
//
//import SwiftUI
//import CoreData
//import CoreLocation
//import Combine
//
//struct ContentView: View {
//
// @State private var counter = 0
// var body: some View {
// VStack {
// ContentView2()
// Button("Malc \(counter)") {
// counter += 1
// }
// }
//
// }
//}
//
//struct ContentView2: View {
//
// @State var timer = Timer.publish(every: 1, on: .main, in:.common).autoconnect()
// @State var locator = CLLocationManager.publishLocation()
// @State private var counter = 0
// @State var locationString = ""
// var body: some View {
// VStack {
// Text("Hello, World! \(counter)")
// Text("Location \(locationString)")
// }
// .onReceive(timer) { time in
//// if self.counter == 5 {
//// self.timer.upstream.connect().cancel()
//// } else {
//// // print("The time is now \(time)")
//// }
// self.counter += 1
// }
// .onReceive(locator) { location in
// locationString = String(format: "%.5f,%.5f %@", location.coordinate.latitude, location.coordinate.longitude, location.timestamp.description)
// }
// }
//}
@melgu
Copy link

melgu commented Feb 3, 2021

Thanks! This helps a lot! I want to use it in one of my apps. Can I do that? If yes, under which license and how do you want to be credited?

@malhal
Copy link
Author

malhal commented Feb 3, 2021

No license or credit required use at own risk, thanks for asking though.

Its not fully tested so I'm not certain the start and stop are in the right place. Also given the location manager is wrapped up in a publisher its not possible to configure its accuracy, etc. Fun to play with though.

@melgu
Copy link

melgu commented Feb 3, 2021

For my current purpose it's enough and a great starting point in any case. Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment