RxSwift官方实例五(定位)

代码下载

定位

搭建UI

构建如下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基类,遵守DelegateProxyTypeCLLocationManagerDelegate协议:

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协议的其他函数均在基类DelegateProxyDelegateProxyType扩展中实现,但是如下两个函数需要满足一定的条件:

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)定义两个禁止外部修改的属性authorizedlocation,定义一个私有常量属性manager
  • 初始化时设置manager的最小更新距离和定位精度,构建两个序列authorizedlocation,并进行数据的转换,最后请求定位权限并开始定位

绑定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)"
        })
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值