Я только начал изучать комбайн, так что для меня это все еще немного нечеткое. Я хотел бы создать пользовательский Publisher
, который будет использовать CLLocationManager
для отображения текущего местоположения пользователя. Я бы хотел, чтобы он работал таким образом, чтобы locationManager
начинал обновлять местоположение только тогда, когда есть подключенные подписчики. И после того, как все подписчики были удалены, отменены и т. Д., Он должен перестать обновлять местоположение. Возможно ли это сделать? Как мне создать такой publisher
? Также это правильный подход или что-то не так?
Swift Combine - Создайте издателя для CoreLocation
Ответы (2)
Основы того, что вы хотите, довольно просты. Есть пример из Использование Combine, в котором CoreLocation заключен в объект, который действует как прокси, возвращая издателя обновлений _ 1_.
Сам CoreLocation не запускает и не останавливает такие вещи автоматически, и в прокси-объекте я скопировал этот шаблон, чтобы вы могли вручную запускать и останавливать процесс обновления.
Ядро кода находится в 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))
}
}
Это не совсем то, что вы просили, поскольку оно не запускает обновления автоматически при подписке, но я подозреваю, что вы могли бы расширить это, чтобы включить эту возможность.
До сих пор я не углублялся в создание собственных издателей, реализуя все методы, требуемые протоколом, поэтому у меня нет подробностей, касающихся этого механизма. Сам Combine имеет концепцию ConnectablePublisher для случаев, когда вам нужен явный контроль над обновлениями, хотя большинство издателей и операторов запускаются либо при создании издателя, либо при подписке.
В общем случае вариант ЕСЛИ лучше подходит для вашего варианта использования. В некоторых случаях вы создаете конвейеры и подписываетесь перед обновлением представлений - в этом случае отказ от запроса фоновых обновлений сэкономит вам некоторую вычислительную мощность и потребление энергии.
В примерах UIKit, в которых используется этот издатель CoreLocation, у меня также есть механизмы для проверки запрошенных разрешений для обновления местоположения, встроенных в пример контроллера представления: https://github.com/heckj/swiftui-notes/blob/master/UIKit-Combine/HeadingViewController.swift а>
Я новичок в Combine, но вот моя попытка, которую я создал сегодня, так что это работа в процессе и, возможно, не было сделано правильно. Идея состоит в том, чтобы использовать CLLocationManager
то, как он был разработан, то есть несколько экземпляров там, где это необходимо.
// Requirements: a NSLocationWhenInUseUsageDescription entry in Info.plist and call requestWhenInUseAuthorization
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)
}
}
}
}
}
Тестировщик
import SwiftUI
import CoreData
import CoreLocation
import Combine
class Locator : ObservableObject {
@Published var location = CLLocation()
var cancellable : AnyCancellable?
init() {
}
func start(){
cancellable = CLLocationManager.publishLocation()
.assign(to: \.location, on: self)
}
}
struct ContentView: View {
@StateObject var locator = Locator()
var body: some View {
VStack {
Text("Location \(locator.location)")
}
.onAppear(){
locator.start()
}
}
}
Затем я планирую добавить publishAuthorization
и создать конвейер. Я также хотел бы предоставить параметр конфигурации для init, чтобы при наличии нескольких подписчиков они могли настроить диспетчер местоположения таким же образом, например distanceFilter
.