定位
搭建UI
构建如下UI:
self.view.addSubview(noGeolocationView)
noGeolocationView.snp.makeConstraints { (maker) in
maker.left.top.right.bottom.equalTo(view)
}
CLLocationManagerDelegate的Rx实现
由于RxCocoa
没有对CLLocationManagerDelegate
实现,所以需要我们自己实现。
构建CLLocationManagerDelegateProxy
定义CLLocationManagerDelegateProxy
类遵继承DelegateProxy
基类,遵守DelegateProxyType
和CLLocationManagerDelegate
协议:
class CLLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {
init(manager: CLLocationManager) {
super.init(parentObject: manager, delegateProxy: CLLocationManagerDelegateProxy.self)
}
static func registerKnownImplementations() {
self.register { CLLocationManagerDelegateProxy(manager: $0) }
}
internal lazy var didUpdateLocationsSubject = PublishSubject<(CLLocationManager, [CLLocation])>()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
forwardToDelegate()?.locationManager?(manager, didUpdateLocations: locations)
didUpdateLocationsSubject.onNext((manager, locations))
}
deinit {
didUpdateLocationsSubject.onCompleted()
}
}
代码分析:
- 初始化
- 实现
DelegateProxyType
协议的registerKnownImplementations
函数,注册一个Rx代理对象(CLLocationManagerDelegateProxy
)生成的闭包 - 实现
CLLocationManagerDelegate
协议的locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
函数,在函数中调用forwardToDelegate
对象的协议方法对正常代理对象的支持,使用Subject
方式将协议中的函数实现为Observeble序列 - 销毁时,
Subject
发送完成的元素结束序列 CLLocationManagerDelegate
协议的其他函数均未实现,到时走消息转发的形式
DelegateProxyType
协议的其他函数均在基类DelegateProxy
或DelegateProxyType
扩展中实现,但是如下两个函数需要满足一定的条件:
extension DelegateProxyType where ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
return object.delegate
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.delegate = delegate
}
}
所以扩展CLLocationManager
让其遵守HasDelegate
协议来满足相应条件达到实现DelegateProxyType
协议的效果:
extension CLLocationManager: HasDelegate {
public typealias Delegate = CLLocationManagerDelegate
}
扩展Reactive
基于CLLocationManager
扩展Reactive
主要是为了方便使用:
extension Reactive where Base: CLLocationManager {
var delegate: CLLocationManagerDelegateProxy {
return CLLocationManagerDelegateProxy.proxy(for: base)
}
var didUpdateLocations: Observable<(CLLocationManager, [CLLocation])> {
return delegate.didUpdateLocationsSubject
}
var didChangeAuthorization: Observable<(CLLocationManager, CLAuthorizationStatus)> {
return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)))
.map { (a) -> (CLLocationManager, CLAuthorizationStatus) in
let manager = try castOrThrow(resultType: CLLocationManager.self, object: a[0])
let number = try castOrThrow(resultType: NSNumber.self, object: a[1])
let status = CLAuthorizationStatus(rawValue: number.int32Value)!
return (manager, status)
}
}
}
private func castOrThrow<T>(resultType: T.Type, object: Any) throws -> T {
guard let resultValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return resultValue
}
代码分析:
delegate
计算属性方便使用- 使用
Subject
方式实现didUpdateLocations
序列 - 使用消息转发方式实现
didChangeAuthorization
序列 - 定义一个私有的全局函数
castOrThrow
处理Any
类型的数据
定位服务
在iOS中使用定位服务,需要在info.plist
文件中配置定位权限询问框的描述,如下所示:
/// 定位服务
class GeoLocationService {
static let instance = GeoLocationService()
// 定位权限
private (set) var authorized: Driver<Bool>
// 定位坐标
private (set) var location: Driver<CLLocationCoordinate2D>
// 定位管理器
private let manager = CLLocationManager()
init() {
// 更新距离
manager.distanceFilter = kCLDistanceFilterNone
// 定位精度
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
authorized = Observable.deferred({ [unowned manager] () -> Observable<(CLLocationManager, CLAuthorizationStatus)> in
let status = CLLocationManager.authorizationStatus()
return manager.rx.didChangeAuthorization.startWith((manager, status))
}).asDriver(onErrorJustReturn: (manager, CLAuthorizationStatus.notDetermined)).map({
switch $0.1 {
case .authorizedAlways:
return true
case .authorizedWhenInUse:
return true
default:
return false
}
})
location = manager.rx.didUpdateLocations.asDriver(onErrorJustReturn: (manager, [CLLocation]())).flatMap({
return $0.1.last.map(Driver.just) ?? Driver.empty()
}).map {
$0.coordinate
}
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
}
}
代码分析:
- 定义一个类属性
instance
缓存一个GeoLocationService
示例对象,使用private (set)
定义两个禁止外部修改的属性authorized
、location
,定义一个私有常量属性manager
- 初始化时设置
manager
的最小更新距离和定位精度,构建两个序列authorized
、location
,并进行数据的转换,最后请求定位权限并开始定位
绑定UI
定义一个打开系统首选项的方法:
/// 打开首选项
private func openAppPreferences() {
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
}
绑定UI:
// 绑定UI
service.authorized
.drive(noGeolocationView.rx.isHidden)
.disposed(by: bag)
service.location
.drive(label.rx.coordinate)
.disposed(by: bag)
button.rx.tap
.bind {[unowned self] in self.openAppPreferences() }
.disposed(by: bag)
button1.rx.tap
.bind {[unowned self] in self.openAppPreferences() }
.disposed(by: bag)
绑定UI部分都相对简单,有个需要注意的地方就是RxCocoa
是没有实现将CLLocationCoordinate2D
绑定到UILabel
的,所以需要扩展Reactive
来实现:
extension Reactive where Base: UILabel {
var coordinate: Binder<CLLocationCoordinate2D> {
return Binder(base, binding: { (label, location) in
label.text = "Lat: \(location.latitude)\nLon: \(location.longitude)"
})
}
}