Skip to content

Instantly share code, notes, and snippets.

@malhal
Last active May 31, 2024 23:28
Show Gist options
  • 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

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