Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add combine location manager extension #1

Merged
merged 6 commits into from May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 96 additions & 0 deletions Sources/ExtensionKit/CoreLocation/AuthorizationPublisher.swift
@@ -0,0 +1,96 @@
import Foundation
import Combine
import CoreLocation


protocol PublisherAuthorizationDelegate: class {
func send(status: CLAuthorizationStatus)
}

protocol SubscriptionAuthorizationDelegate: class {
func requestAuthorization(type: CLLocationManager.AuthorizationType)
}

final class AuthorizationSubscription <S: Subscriber>: NSObject,
PublisherAuthorizationDelegate,
Subscription where S.Input == CLAuthorizationStatus,
S.Failure == Never {

typealias Output = CLAuthorizationStatus
typealias Failure = Never

var subscriber: S?
private weak var delegate: SubscriptionAuthorizationDelegate?
private let authorizationType: CLLocationManager.AuthorizationType

init(
subscriber: S,
authorizationType: CLLocationManager.AuthorizationType,
delegate: SubscriptionAuthorizationDelegate
) {
self.subscriber = subscriber
self.delegate = delegate
self.authorizationType = authorizationType
}

func request(_ demand: Subscribers.Demand) {
delegate?.requestAuthorization(type: authorizationType)
}

func cancel() {
subscriber = nil
delegate = nil
}

func send(status: CLAuthorizationStatus) {
_ = subscriber?.receive(status)
}
}

final class AuthorizationPublisher: NSObject,
Publisher,
CLLocationManagerDelegate,
SubscriptionAuthorizationDelegate {

typealias Output = CLAuthorizationStatus
typealias Failure = Never

private let manager: CLLocationManager
private let authorizationType: CLLocationManager.AuthorizationType
private weak var publisherAuthorizationDelegate: PublisherAuthorizationDelegate?

init(manager: CLLocationManager, authorizationType: CLLocationManager.AuthorizationType) {
self.manager = manager
self.authorizationType = authorizationType
super.init()
self.manager.delegate = self
}

func receive<S>(subscriber: S) where S: Subscriber, AuthorizationPublisher.Failure == S.Failure, AuthorizationPublisher.Output == S.Input {
let subscription = AuthorizationSubscription(
subscriber: subscriber,
authorizationType: authorizationType,
delegate: self
)
subscriber.receive(subscription: subscription)
publisherAuthorizationDelegate = subscription
}

// MARK: - CLLocationManagerDelegate

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
publisherAuthorizationDelegate?.send(status: status)
}

// MARK: - AuthorizationSubscriptionDelegate

func requestAuthorization(type: CLLocationManager.AuthorizationType) {
switch type {
case .whenInUse:
manager.requestWhenInUseAuthorization()
case .always:
manager.requestAlwaysAuthorization()
}
}
}

55 changes: 55 additions & 0 deletions Sources/ExtensionKit/CoreLocation/CLLocationManager.swift
@@ -0,0 +1,55 @@
import Combine
import CoreLocation

public extension CLLocationManager {

/// Request locaton authorization and subscribe to `CLAuthorizationStatus` updates
/// - Parameters:
/// - manager: `CLLocationManager`
/// - type: `AuthorizationType`
/// - Returns: Publisher with `AuthorizationType`
static func requestLocationAuthorization(
with manager: CLLocationManager = .init(),
type: AuthorizationType
) -> AnyPublisher<CLAuthorizationStatus, Never> {
AuthorizationPublisher(manager: manager, authorizationType: type)
.eraseToAnyPublisher()
}

/// Request locaton **always** authorization `CLAuthorizationStatus` with **upgrade** prompt (experimental)
/// - Parameters:
/// - manager: `CLLocationManager`
/// - Returns: Publisher with `AuthorizationType`
static func requestLocationAlwaysAuthorization(
with manager: CLLocationManager = .init()
) -> AnyPublisher<CLAuthorizationStatus, Never> {
AuthorizationPublisher(manager: manager, authorizationType: .always)
.flatMap { status -> AnyPublisher<CLAuthorizationStatus, Never> in
if status == CLAuthorizationStatus.authorizedAlways {
return AuthorizationPublisher(
manager: manager,
authorizationType: .always
)
.eraseToAnyPublisher()
}
return Just(status).eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}

/// Receive location updates from the `CLLocationManager`
/// - Parameter manager: `CLLocationManager`
/// - Returns: Publisher with `[CLLocation]` or `Error`
static func receiveLocationUpdates(
from manager: CLLocationManager = .init()
) -> AnyPublisher<[CLLocation], Error> {
LocationPublisher(manager: manager)
.eraseToAnyPublisher()
}

/// Authorization access level
enum AuthorizationType: String {
case always, whenInUse
}

}
91 changes: 91 additions & 0 deletions Sources/ExtensionKit/CoreLocation/LocationPublisher.swift
@@ -0,0 +1,91 @@
import Combine
import CoreLocation

protocol PublisherLocationDelegate: class {
func startLocationUpdates()
}

protocol SubscriptionLocationDelegate: class {
func didUpdate(with locations: [CLLocation])
func didFail(with error: Error)
}

final class LocationSubscription <S: Subscriber>:
NSObject,
SubscriptionLocationDelegate,
Subscription where S.Input == [CLLocation],
S.Failure == Error {

var subscriber: S?
private weak var publisherDelegate: PublisherLocationDelegate?

init(subscriber: S?, delegate: PublisherLocationDelegate?) {
self.subscriber = subscriber
self.publisherDelegate = delegate
}

func didUpdate(with locations: [CLLocation]) {
_ = subscriber?.receive(locations)
}

func didFail(with error: Error) {
_ = subscriber?.receive(completion: .failure(error))
}

func request(_ demand: Subscribers.Demand) {
publisherDelegate?.startLocationUpdates()
}

func cancel() {
subscriber = nil
publisherDelegate = nil
}

}

final class LocationPublisher: NSObject,
Publisher,
CLLocationManagerDelegate,
PublisherLocationDelegate {
typealias Output = [CLLocation]
typealias Failure = Error

private let manager: CLLocationManager
private weak var subscriberDelegate: SubscriptionLocationDelegate?

init(manager: CLLocationManager) {
self.manager = manager
super.init()
self.manager.delegate = self
}

// MARK: Publisher

func receive<S>(subscriber: S) where S : Subscriber, LocationPublisher.Failure == S.Failure, LocationPublisher.Output == S.Input {
let subscibtion = LocationSubscription(
subscriber: subscriber,
delegate: self
)
subscriber.receive(subscription: subscibtion)
subscriberDelegate = subscibtion
}

// MARK: CLLocationManagerDelegate

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
subscriberDelegate?.didFail(with: error)
manager.stopUpdatingLocation()
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
subscriberDelegate?.didUpdate(with: locations)
}

// MARK: PublisherLocationDelegate

func startLocationUpdates() {
manager.startUpdatingLocation()
}
}