[RxSwift] AirPortClone: CoreLocation

Junyoung Park·2022년 12월 28일
0

RxSwift

목록 보기
21/25
post-thumbnail

#9 Distance Calculation using CoreLocation - RxSwift MVVM Coordinator iOS App

AirPortClone: CoreLocation

구현 목표

  • CoreLocation을 통한 현재 위치 및 거리 측정 로직 구현

구현 태스크

  • 현재 위치와 특정 위치 간의 계산 자동화

핵심 코드

final class LocationService: NSObject {
    static let shared = LocationService()
    private let manager = CLLocationManager()
    private let currentLocationRelay: BehaviorRelay<(lat: Double, lon: Double)?> = .init(value: nil)
    lazy var currentLocation: Observable<(lat: Double, lon: Double)?> = self.currentLocationRelay.asObservable().share(replay: 1, scope: .forever)
    
    private override init() {
        super.init()
        setLocationService()
    }
    
    private func setLocationService() {
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        manager.requestAlwaysAuthorization()
        
        if CLLocationManager.locationServicesEnabled() {
            manager.startUpdatingLocation()
        }
    }
    
    deinit {
        manager.stopUpdatingLocation()
    }
}

extension LocationService: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        let currentLocation = (lat: location.coordinate.latitude, lon: location.coordinate.longitude)
        currentLocationRelay.accept(currentLocation)
        manager.stopUpdatingLocation()
    }
}
  • currentLocationRelayBehaviorRelay 타입으로 지역을 표현하는 튜플을 담아두는 릴레이
  • Observable을 통해 해당 지역 튜플 데이터 관찰 가능
  • didUpdateLocations를 통해 건네받은 현재 위치를 currentLocationRelay로 넘기기
  • 싱글턴 패턴 구현
override func start() {
        let vc = AirportsViewController()
        let locationService = LocationService.shared
        vc.viewModelBuilder = { [models] in
            let title = models.first?.city ?? ""
            return AirportsViewModel(input: $0, dependencies: (title: title, models: models, currentLocation: locationService.currentLocation))
        }
        self.navigationController.pushViewController(vc, animated: true)
    }
  • AirportsCoordinator에서 start()를 통해 다른 뷰 컨트롤러를 시동하는 구조
  • dependencies를 통해 뷰 모델을 빌드하고 있는데, 현 시점의 currentLocation을 읽어오는 곳이 바로 현 시점.
private static func getDistance(airportLocation: (lat: Double?, lon: Double?),
                             currentLocation: (lat: Double, lon: Double)) -> Double? {
        guard
            let lat = airportLocation.lat,
            let lon = airportLocation.lon else { return nil }
        let current = CLLocation(latitude: currentLocation.lat, longitude: currentLocation.lon)
        let airport = CLLocation(latitude: lat, longitude: lon)
        return current.distance(from: airport)
    }
  • API를 통해 읽어온 데이터와 현재 위치 값의 거리를 자동으로 계산해서 리턴하는 static 함수
self.distance = AirportViewModel.getDistance(airportLocation: (lat: Double(model.lat), lon: Double(model.lon)), currentLocation: currentLocation)
  • 해당 함수를 통해 뷰 모델의 distance 값이 자동으로 계산
extension AirportViewModel: Comparable {
    static func < (lhs: AirportViewModel, rhs: AirportViewModel) -> Bool {
        return lhs.distance ?? 0 < rhs.distance ?? 0
    }
    
    static func == (lhs: AirportViewModel, rhs: AirportViewModel) -> Bool {
        return lhs.code == rhs.code
    }
}
  • 현재 Comparable 프로토콜을 준수하는 뷰 모델은 <, == 함수를 적용했기 때문에 이후 여러 개의 거리를 가진 뷰 모델이 테이블 뷰에 들어오기 전 sorted로 정렬 가능한 형태

구현 화면

profile
JUST DO IT

0개의 댓글