ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift 위치정보를 받아서 행정구역 단위로 나타내기(역 지오코딩, reverse geocoding)
    iOS/Swift 2022. 1. 13. 02:33

    안녕하세요!! 이번엔 역 지오코딩을 구현하는 방법을 정리했습니다.

    이번 글도 노션에서 옮겨와 말이 짧습니다!! 양해해주세용😽


     

    mkMapView를 공부하면서 위치정보를 나타내는 것에 대해 작성해보려고 한다.

     

    먼저, 사용자의 위치정보를 받아오려면 위치정보에 대한 권한을 요청해야한다. Info.Plist에 위치와 관련된 속성들이 있다.

    • Privacy - Location When In Use Usage DescriptioniOS
      • 앱이 foreground에서 실행 중일 때에만 위치 정보에 액세스하는 경우 사용한다.
      • iOS 11 이상에서 사용 가능하다.
    • Privacy - Location Always and When In Use Usage DescriptioniOS
      • 앱이 background에서 실행되는 동안 위치 정보에 액세스하는 경우 사용한다.
      • iOS 11 이상에서 사용 가능하다.
    • Privacy - Location Default Accuracy Reduced
      • 위치 정확도에 대한 앱의 동작을 설정하려면 Info.Plist에 이 키를 포함할 수 있다.
      • true: 위치 정확도가 떨어지는지 묻는 메시지가 표시되도록 키 값을 설정한다.
        • reducedAccuracy만을 이용한다.
        • 대략적인 위치만을 제공한다.
      • false: 전체 위치 정확도를 묻는 메세지가 표시되도록 설정한다.
        • fullAccuracy와 reducedAccuracy 이용가능
        • 사용자가 정확한 위치를 제공할지 말지 선택할 수 있다.
      • 키를 명시해두지 않을 경우, false가 기본값으로 설정된다.
      • iOS 14 이상에서 사용 가능하다.
    • Privacy - Location Temporary Usage Description Dictionary
      • 앱이 사용자 위치에 대한 임시 액세스를 요청하는 이유를 설명한다.
      • 예를 들어, 앱이 앱의 한 부분에서 근처에 있는 카페를 제안하고, 다른 부분에서 근처에 있는 친구를 찾는 경우 두 항목을 포함한다. → 친구를 찾아야 할 때 친구를 찾기위해 위치정보가 필요하다고 요청, 카페를 찾을 때 위치정보가 필요하다고 요청
      • 앱이 사용자 위치에 대한 임시 액세스를 요청하는 이유를 설명한다.

    • Privacy - Location Usage Description
      • macOS앱이 사용자의 위치 정보에 액세스하는 API를 사용하는 경우에만 필요

    이중에서 앱이 사용중일 경우에만 지도를 사용할 것이기 때문에

    Privacy - Location When In Use Usage Description 속성을 Info.plist에 추가해주었다.

     

    그리고 미리 MapKit을 이용하여 만들어둔 ViewController로 가서 CoreLocation을 import 해주었다.

     

    var locataionManager: CLLocationManager = CLLocationManager()
    
    override func viewDidLoad() {
            super.viewDidLoad()
            locataionManager.delegate = self
    }
    

    그리고 위치 관련 이벤트를 처리하기 위해 CLLocationManager 객체를 만들어주고 delegate 메소드를 사용하기 위해 viewDidLoad에서 delegate 시켜주었다.

     

    그리고 사용자의 위치 권한을 확인하는 코드를 추가해주었다.

    // 사용자의 위치 서비스 권한 확인
        func checkUsersLocationServicesAutorization() {
            let auth: CLAuthorizationStatus
            
            if #available(iOS 14.0, *) {
                auth = locataionManager.authorizationStatus
            } else {
                auth = CLLocationManager.authorizationStatus()
            }
    
            // iOS 위치 서비스 권한 확인
            if CLLocationManager.locationServicesEnabled() {
                checkCurrentLocationAutorization(status: auth)
            }
            
        }
    
    func checkCurrentLocationAutorization(status: CLAuthorizationStatus) {
            switch status {
            case .notDetermined:
                print("설정되지 않았음")
                locataionManager.requestWhenInUseAuthorization()
                locataionManager.desiredAccuracy = kCLLocationAccuracyBest
                locataionManager.startUpdatingLocation()
    
    				// 위치정보를 받아올 수 없는 상황이거나 사용자가 거부했을 경우, 서울시청을 기본 Location으로 지정
            case .restricted, .denied:
                setLocation(latitude: 37.56674435790457, longitude: 126.9784350966443)
                print("제한됨. 설정으로 이동")
            case .authorizedAlways, .authorizedWhenInUse:
                print("이용가능")
                self.locataionManager.startUpdatingLocation()
                
            @unknown default:
                print("unknown")
            }
            
            if #available(iOS 14.0, *) {
                let accuracyState = locataionManager.accuracyAuthorization
                
                switch accuracyState {
                case .fullAccuracy:
                    print("Full")
                case .reducedAccuracy:
                    print("reduced")
                @unknown default:
                    print("Default")
                }
            }
        }
    

    위 함수에서 startUpdatingLocation을 호출하여, 사용자의 현재 위치에 대해 업데이트를 시작할 수 있다.

    실제로 값을 받게 되면, CLLocationManagerDelegate 프로토콜의 didUpdateLocations 메소드가 실행된다.

     

    extension MapViewController: CLLocationManagerDelegate {
    
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            if let coordinate = locations.last?.coordinate {
    
    			// location 정보를 받아 행정구역을 나타낼 수 있는 getCurrentAddress함수 (커스텀)
                getCurrentAddress(location: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
    
    			// 위도 경도를 담아둘 프로퍼티를 만들었다.
                self.locationCoordinator = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
    
    			// 작업이 끝날때 stopUpdatingLocation을 호출해 불필요한 위치 데이터 업데이트를 멈추게 한다.
                locataionManager.stopUpdatingLocation()
            }
        }
        
    	// 현재 위치 정보를 가져오는 것을 실패했을 때
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print("didFailWithError: \\(error)")
        }
        
    
    	// iOS 14 이상에서 사용 가능
        func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
            print(#function)
            checkUsersLocationServicesAutorization()
        }
        
    	// iOS 14 미만에서 사용 가능
        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            print(#function)
            checkUsersLocationServicesAutorization()
        }
    }

    didUpdateLocations의 메소드의 매개변수들은

    manager는 locationManager, locations는 CLLocation의 배열이 들어온다.

    이 배열에는 마지막 위치에 대한 데이터를 포함하고 있다.

    데이터를 전송하기 전에 여러 위치에 대한 정보가 저장될 수 있기 때문에 배열로 들어오고,

    가장 최근의 위치가 배열의 가장 끝에 위치한다.

     

    func getCurrentAddress(location: CLLocation) {
            let coder = CLGeocoder()
            let locale = Locale(identifier: "ko-KR")
            coder.reverseGeocodeLocation(location, preferredLocale: locale) { (placemark, error) -> Void in
                guard error == nil, let place = placemark?.first else {
                    print("주소 설정 불가능")
                    return
                }
                if let subLocality = place.subLocality {
                    self.locationAddress += " \\(subLocality)"
                    print(subLocality)
                }
            }
        }
    

    getCurrentAddress 함수는 location 정보를 받아 행정구역으로 decoding 해주는 함수다.

    한국어로 출력하기 위해 한국 식별자를 가지고 있는 locale 객체를 만들어주었다.

    CLGeocoder의 reverseGeocodeLocation를 호출해서 placemark들을 받아올 수 있는데, placemark에는 국가, 지역, 주, 도시 및 거리주소 등의 정보가 포함되어있다.

    • administrativeArea
      • 시/도
    • locality
    • subLocality
    var locationAddress: String = UserDefaults.standard.string(forKey: "location") ?? "" {
            didSet {
                self.navigationItem.title = locationAddress
                UserDefaults.standard.set(locationAddress, forKey: "location")
            }
    }
    

    위에서 가져온 위치 정보를 다른 곳에서도 사용할 수 있으니 UserDefaults에 저장했다. 프로퍼티 옵저버를 달아놓고 위치가 바뀌어서 localAddress가 바뀔 때 마다 재설정 될 수 있도록 코드를 작성했다.

    시뮬레이터를 실행하면 navigationTitle에 원하는 행정구역으로 작성이 된 것을 볼 수 있다! 😃

    댓글

Designed by Tistory.