관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. Observable로 Wrapping하여 권한 요청) 위치 권한 요청, 실시간 위치 정보 획득 본문

iOS 응용 (swift)

[iOS - swift] 1. Observable로 Wrapping하여 권한 요청) 위치 권한 요청, 실시간 위치 정보 획득

jake-kim 2022. 1. 4. 23:20

1. Observable로 Wrapping하여 권한 요청) 위치 권한 요청, 실시간 위치 정보 획득

2. Observable로 Wrapping하여 권한 요청) 사진 권한, 카메라 권한

3. Observable로 Wrapping하여 권한 요청) 마이크 권한, ATT(App Tracking Transparency) 권한

4. Observable로 Wrapping하여 권한 요청) RxSwift의 concat을 이용하여 순서대로 권한 요청 방법

Observable로 wrapping 작업 핵심

  • 기존에 Observable 형태를 리턴해주는 작업이면, RxSwift의 생성자 연산자 중에 deferred 연산자 사용하여 wrapping (해당 글)
  • 기존에 Observable 형태가 아니고 클로저 형태로 값을 받는 경우, create 연산자 사용 (사진 권한, 카메라 권한 글 참고)

필요한 framework, 클래스 준비

  • RxCoreLocation은 실시간 위치 정보를 받아올 때 사용
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxCoreLocation'
  • LocationPermissionManager라는 클래스를 만들어서 싱글톤으로 사용
import RxSwift
import RxCocoa
import RxCoreLocation
import CoreLocation

class LocationPermissionManager {
  static let shared = LocationPermissionManager()
  private let disposeBag = DisposeBag()
  
  // TODO: Observable로 wrapping한 권한 설정 코드 구현
}

위치 권한 코드를 Observable로 Wrapping 작업

  • info.plist에 privacy - Location When In Use Usage Description 추가

  • PermissionManager 클래스에 위치관련 프로퍼티 추가
    • locationSubject: subject를 사용하여 위치 정보를 실시간으로 받기 방출 & 구독하기 위함
      (Subject는 Hot Observable이므로 구독하고 있는 곳이 여러곳일때도 사용 가능)

      * subejct란? Hot Observable (muticast, 즉시 방출)
          cf) single, Observable란? Cold Observable (unicast, 구독 시 방출)

    • locationManager: 권한 request, 실시간 위치정보 받아오기 역할
      let locationSubject = BehaviorSubject<CLLocationCoordinate2D?>(value: nil)
      private let locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.distanceFilter = kCLDistanceFilterNone
        return manager
      }()​
  • private init()에서 실시간 위치 구독하여 locationSubject에 입력
  • 위치 권한 요청
    • Observable 지연 생성 연산자인 deferred를 사용하여 위치 권한 요청
    • (비동기작업) 위치 권한을 사용자가 선택한 후 Observable을 리턴해야하므로, rx.didChangeAuthorization을 리턴
      func requestLocation() -> Observable<CLAuthorizationStatus> {
        return Observable<CLAuthorizationStatus>
          .deferred { [weak self] in
            guard let ss = self else { return .empty() }
            ss.locationManager.requestWhenInUseAuthorization()
            return ss.locationManager.rx.didChangeAuthorization
              .map { $1 }
              .filter { $0 != .notDetermined }
              .do(onNext: { _ in ss.locationManager.startUpdatingLocation() })
              .take(1)
          }
      }​

      cf) Observable 생성 연산자에는 대표적으로 createdeferred가 존재
            * deferred:  내부에서 다른 Observable을 리턴할 때 주로 사용하고,
            * create: onNext, onError, complete, Disposable.create()를 직접 리턴할때 사용
  • 전체 코드
    import RxSwift
    import RxCocoa
    import RxCoreLocation
    import CoreLocation
    
    class LocationPermissionManager {
      static let shared = LocationPermissionManager()
      private let disposeBag = DisposeBag()
      
      let locationSubject = BehaviorSubject<CLLocationCoordinate2D?>(value: nil)
      private let locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.distanceFilter = kCLDistanceFilterNone
        return manager
      }()
      
      private init() {
        // Bind Location
        self.locationManager.rx.didUpdateLocations
          .compactMap(\.locations.last?.coordinate)
          .bind(onNext: self.locationSubject.onNext(_:))
          .disposed(by: self.disposeBag)
        self.locationManager.startUpdatingLocation() // 이미 권한을 허용한 경우 케이스 대비
      }
      
      func requestLocation() -> Observable<CLAuthorizationStatus> {
        return Observable<CLAuthorizationStatus>
          .deferred { [weak self] in
            guard let ss = self else { return .empty() }
            ss.locationManager.requestWhenInUseAuthorization()
            return ss.locationManager.rx.didChangeAuthorization
              .map { $1 }
              .filter { $0 != .notDetermined }
              .do(onNext: { _ in ss.locationManager.startUpdatingLocation() })
              .take(1)
          }
      }
    }
  • 사용하는 쪽
    • 실시간 위치 데이터 구독하여 획득
      override func viewDidLoad() {
        super.viewDidLoad()
        
        LocationPermissionManager.shared.locationSubject
          .compactMap { $0 }
          .bind { [weak self] in self?.locationLabel.text = "\($0)" }
          .disposed(by: self.disposeBag)
      }​
    • 위치 권한 요청
      // 위치 권한 요청
      @IBAction func didTapButton(_ sender: Any) {
        LocationPermissionManager.shared.requestLocation()
          .bind { print($0) }
          .disposed(by: self.disposeBag)
      }​

* 전체 코드: https://github.com/JK0369/ExLocationPermission

Comments