[iOS] CoreLocation - CLGeocoder

Inwoo Hwang·2021년 8월 26일
0

iOS

목록 보기
13/13
post-thumbnail

저는 지금 위치 정보 권한을 허용한 다음 허용된 데이터를 가지고 화면에 사용자의 주소를 보여주려고 하고 있는데요...CLLocation을 통해 받아 올 수 있는 건 사용자 기기의 좌표 그리고 경도와 위도 밖에 없는데... 이걸로 어떻게 사용자의 주소를 알아 낼 수 있을지 막막하더라구요.

그래서 열심히 구글링을 하다가 드디어 찾았습니다! CLGeocoder를 이용하면 되네요! 그리고 역시 애플 공식문서!! 친절하게 어떻게 CLGeocoder를 활용해서 받아온 좌표값을 가지고 주소로 변환할 수 있는지 적혀있었습니다.

CLGeocoder


지역좌표와 지역 이름간의 변환을 해주는 인터페이스

오케이..변환 해 주는 인터페이스인건 알겠는데 어떻게 변환을 할 수 있는거죠?

음 읽어보니 geoCoder 객체는 일회성 객체이구요 네트워크에 기반된 서비스를 활용해서 특정한 좌표위치값을 받아 사용자가 이해할 수 있는 주소를 가져오고 또 반대로 주소를 통해 좌표 정보를 받아 올 수 있다고 합니다.

자세한 내용은 Converting Between Coordinates and User-Friendly Places Names 라는 글에서 확인 하실 수 있어요

Converting a Coordinate into a Placemark

공식 문서에 따르면 만약 CLLocation 객체를 갖고 있으면 reverseGeocodeLocation(_:completionHandler:) 메서드를 호출하여 사용자의 위치에 해당되는 CLPlacemark 객체를 얻어 올 수 있다네요. 보통 이러한 작업은 유저가 선택한 지도의 위치 주소를 문자열로 유저에게 보여주려 할 때 한다고 합니다. 제가 딱 필요한 정보에요!

친절하게 코드예시까지 적혀있습니다.

func lookUpCurrentLocation(completionHandler: @escaping (CLPlacemark?)
                -> Void ) {
    // Use the last reported location.
    if let lastLocation = self.locationManager.location {
        let geocoder = CLGeocoder()
            
        // Look up the location and pass it to the completion handler
        geocoder.reverseGeocodeLocation(lastLocation, 
                    completionHandler: { (placemarks, error) in
            if error == nil {
                let firstLocation = placemarks?[0]
                completionHandler(firstLocation)
            }
            else {
	         // An error occurred during geocoding.
                completionHandler(nil)
            }
        })
    }
    else {
        // No location was available.
        completionHandler(nil)
    }
}

역시 네트워크 기반 작업이라 그런지 completion handler를 활용하여 비동기작업에 대한 처리를 하는 것 같아요. 저 placemarks를 통해서 유저의 위치 정보를 얻을 수 있을 것 같다는 생각이 계속 드네요. 그럼 바로 해 봐야죠!

먼저 유저 위치 정보 모델 클래스를 구현했습니다~!

class UserLocationInformation {
    var country: String
    var administrativeArea: String
    var subAdministrativeArea: String?
    var locality: String
    var thoroughFare: String
    var subThoroughFare: String
    
    init(country: String, administrativeArea: String, locality: String, thoroughFare: String, subThoroughFare: String) {
        self.country = country
        self.administrativeArea = administrativeArea
        self.locality = country
        self.thoroughFare = thoroughFare
        self.subThoroughFare = subThoroughFare
    }
}

그리고 위치 정보를 받아 주소로 변환 해 주는 메서드를 하나 구현 해 보았습니다. 제가 생각한 방법은 geoCoder 객체를 생성한 뒤 reverseGeocodeLocation 메서드를 통해서 구해온 placemark에 대한 주소를 모델 초기화를 통해 가져오려 했습니다.

이 작업이 비동기 작업이기 때문에 시점 문제를 맞닥드렸고 해결책으로는 고심 끝에 @escaping closure를 활용하여 UserLocationInformation 객체 초기화작업을 탈출 클로저로 보냈습니다.

   private func convertLocationToAddress(with clLocation: CLLocation, localeIdentifier: String, completionHandler: @escaping (UserLocationInformation) -> ()) {
        let locale = Locale(identifier: localeIdentifier)

        let geoCoder = CLGeocoder()
        geoCoder.reverseGeocodeLocation(clLocation, preferredLocale: locale) { placemarks, error in

            if error != nil {
                NSLog("\(String(describing: error))")
                return
            }
            guard let placemark = placemarks?.first,
                  let country = placemark.country,
                  let administrativeArea = placemark.administrativeArea,
                  let locality = placemark.locality,
                  let thoroughFare = placemark.locality,
                  let subThoroughFare = placemark.subThoroughfare
                  else { return }
            completionHandler(UserLocationInformation(country: country, administrativeArea: administrativeArea, locality: locality, thoroughFare: thoroughFare, subThoroughFare: subThoroughFare))
        }
    }

그후 아래와 같이 CLLocationManagerDelegate을 채택하고 있는 ViewController의 메서드 중 사용자권환이 바뀌는 시점에 호출 되는 메서드 속에서 탈출클로저를 통해 전달 된 클로저를 받아 UI설정을 해 주었습니다. (UI 설정은 꼭 꼭 메인 쓰레드에서 해줘야 하는 부분 까먹으면 안돼요!!) 물론 전제 조건은 사용자가 위치권한을 허락한 경우에만 위치 값을 이용할 수 있는 것이죠!

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {

        if manager.authorizationStatus == .authorizedWhenInUse {
            guard let coordinate = manager.location?.coordinate else {
                NSLog("Can't locate coordinate")
                return

            }
            let clLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
            
            guard let localeIdentifier = Locale.preferredLanguages.first else { return }
            
            convertLocationToAddress(with: clLocation, localeIdentifier: localeIdentifier) { information in
                DispatchQueue.main.async {
                    self.addLeftBarButtonItems(title: information.thoroughFare)
                }
            }
        }

시뮬레이터를 한 번 돌려보면!!

짠! 주소 정보를 받고 UI에 적용 되는 것을 확인 했어요!

위치정보를 공부하는게 처음이라 아직 어색하긴 하지만 열심히 해서 더 깔끔한 코드를 짤 수 있게 노력해야 겠습니다!

[참고]:

Apple Developer Document | CLGeocoder

Apple Developer Document | Converting Between Coordinates and User-Friendly Place Names

profile
james, the enthusiastic developer

0개의 댓글