저는 지금 위치 정보 권한을 허용한 다음 허용된 데이터를 가지고 화면에 사용자의 주소를 보여주려고 하고 있는데요...CLLocation을 통해 받아 올 수 있는 건 사용자 기기의 좌표 그리고 경도와 위도 밖에 없는데... 이걸로 어떻게 사용자의 주소를 알아 낼 수 있을지 막막하더라구요.
그래서 열심히 구글링을 하다가 드디어 찾았습니다! CLGeocoder를 이용하면 되네요! 그리고 역시 애플 공식문서!! 친절하게 어떻게 CLGeocoder를 활용해서 받아온 좌표값을 가지고 주소로 변환할 수 있는지 적혀있었습니다.
지역좌표와 지역 이름간의 변환을 해주는 인터페이스
오케이..변환 해 주는 인터페이스인건 알겠는데 어떻게 변환을 할 수 있는거죠?
음 읽어보니 geoCoder 객체는 일회성 객체이구요 네트워크에 기반된 서비스를 활용해서 특정한 좌표위치값을 받아 사용자가 이해할 수 있는 주소를 가져오고 또 반대로 주소를 통해 좌표 정보를 받아 올 수 있다고 합니다.
자세한 내용은 Converting Between Coordinates and User-Friendly Places Names 라는 글에서 확인 하실 수 있어요
공식 문서에 따르면 만약 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