[iOS] Location Manager

RudinP·2024년 3월 24일
0

Study

목록 보기
206/226

CLLocationManager

  • CoreLocation 프레임워크에 해당하는 것들은 접두사로 CL이 붙어있다.

위치와 관련된 기능을 사용 가능한지 알려주는 속성

  • 위치 정보는 매번 사용할 수 없음
    • 권한
    • 설정
    • 에어플레인 모드
  • locationServicesEnabled()를 가장 많이 사용한다.
    • 디바이스에서 위치 서비스가 활성화되어 있을 때 true 리턴
    • 사용자가 위치 설정에서 끄고 켤 수 있다.
    • 이럴 때마다, didChangeAuthorization 메소드가 호출됨

위치 서비스를 꺼둔 경우 설정창으로 이동하도록 구현

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        if CLLocationManager.locationServicesEnabled(){
            
        } else {
            let alert = UIAlertController(title: "알림", message: "위치 서비스를 활성화시켜 주세요.", preferredStyle: .alert)
            
            let settingAction = UIAlertAction(title: "설정", style: .cancel){ _ in
                //string에 저장된 문자열로 url을 만든 다음에 open으로 전달하면 설정 앱으로 바로 이동 가능
                if let url = URL(string: UIApplication.openSettingsURLString){
                    UIApplication.shared.open(url)
                }
            }
            alert.addAction(settingAction)
            
            let cancelAction = UIAlertAction(title: "취소", style: .cancel)
            alert.addAction(cancelAction)
            
            present(alert, animated: true)
        }
    }

Delegate

locationManagerDidChangeAuthorization(_:)

  • locationManager 객체를 생성할 때 호출
  • 위치 서비스 허가가 바뀔 때에도 반복적으로 호출
    • 사용자가 설정앱에서 위치 설정을 바꿀 때
    • 설정 앱에서 위치 서비스를 켜고 끌 때
    • 위치 및 개인정보 설정을 초기화 할 때
  • 허가 상태는 authorizationStatus, accuracyAuthorization 속성으로 확인하면 된다.
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .notDetermined:
            //사용자가 허가 여부를 결정하지 않은 상태(앱 설치 시 기본 상태)
            break
        case .restricted:
            //스크린타임-콘텐츠 및 개인정보 보호 제한-위치 서비스에서 끄거나, 자녀 앱에서 위치 서비스를 끈 경우
            break
        case .denied:
            //사용자가 허가 요청을 거부, 설정앱에서 위치 서비스를 끈 상태
            break
        case .authorizedAlways:
            //항상 허용
            break
        case .authorizedWhenInUse:
            //앱을 사용할때만 허용
            break
        @unknown default:
            break
        }
    }

locationManager didFailWithError

  • 이 메소드를 구현하지 않으면 CL이 위치 서비스를 사용하려고 할 때 예외를 던진다.
  • 필수 메소드는 아니지만, 이 메소드를 추가하여 에러처리를 하는 것이 바람직하다.
  • 위치서비스가 즉시 위치를 사용할 수 없을 때, CLError.Code.locationUnknown 에러를 보고하고 계속해서 시도한다.
    • 그냥 무시해도 되고, 조금 있다가 새로운 위치가 표시됨.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        //에러를 확인하려면 NSError타입으로 캐스팅해야함.
        let error = error as NSError
        guard error.code != CLError.Code.locationUnknown.rawValue else {return}
        
        print(error)
    }

locationManager didUpdateLocations

  • 위치가 바뀔 때마다 이 메소드가 호출됨
  • CLLocation: 위도, 경도, 고도가 저장되어있는 객체
  • locations에는 최소한 하나의 데이터가 저장되어 있음
    • 위치는 바로 갱신되지만, 가끔 지연으로 인해 앱에서는 이 배열에 두 개의 위치가 저장되는 경우가 있는데, 이럴 때에는 마지막에 있는 객체가 최근 위치이다.
  • 필수 메소드는 아니지만, 구현이 권장된다.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let currentLocation = locations.last{
            latitudeLabel.text = "\(currentLocation.coordinate.latitude)"
            longtitudeLabel.text = "\(currentLocation.coordinate.longitude)"
        }
    }

Requesting Authorization

  • 사용자에게 위치서비스를 사용할 수 있도록 허가를 요청하는 기능

WhenInUse Authorization

  • 사용하게 되면 사용자에게 경고창이 표시됨.
  • 한 번 허용: 일회용 권한으로, 앱을 다시 실행하면 같은 경고창을 표시한다.
  • 앱을 사용하는 동안 허용: 앱을 포어그라운드에서 사용할 때만 위치 정보 사용 가능. 앱을 삭제하거나, 설정에서 권한 정보를 바꾸기 전까지 계속 유지 됨.

  • Info.plist에 NSLocationWhenInUseUsageDescription을 추가하고 값을 설정하면 경고창에 설정해준 내용이 표시된다.
  • 위치 데이터가 왜 필요한지, 어떻게 사용할 건지 자세하게 입력해야 한다.
    • 심사 통과에 중요하다.

  • Raw Keys and Values를 선택하면 NSLocationWhenInUseUsageDescription으로 표시된다.

WhenInUse로 허가 요청하기

    override func viewDidLoad() {
        super.viewDidLoad()
        
        manager.delegate = self
        manager.requestWhenInUseAuthorization()
    }

Always Authorization

  • Always로 요청하기 전에 WhenInUse로 먼저 요청해야 한다.

Always 사용하기

switch manager.authorizationStatus {
...
		case .authorizedAlways:
            manager.startUpdatingLocation()
            break
        case .authorizedWhenInUse:
            manager.requestAlwaysAuthorization()
        @unknown default:
            break
        }

  • 팝업에서 앱을 사용하는 동안 허용을 선택 시, WhenInUse와 Always중 고를 수 있게 표시된다.

WhenInUse vs Always

  • 언제 위치를 사용할 수 있는가
    • WhenInUse: 기본적으로 앱이 포어그라운드에서 실행될 때 위치 사용 가능. 백그라운드 서비스를 등록하고 나면 백그라운드에서도 사용 가능. 앱을 완전히 종료하면 위치 관련 사용 불가.
      • 기본 위치 서비스, 비콘 레인징, 헤딩, 지오코딩만 사용 가능
    • Always: 포어그라운드, 백그라운드 상관 없이 항상 위치 사용 가능. 앱을 완전히 종료한 상태에서 새로운 위치 이벤트가 발생하면 앱이 백그라운드에서 시작됨.
      • 지원하는 서비스에 제한이 없다.

따라서, 기본적으로는 WhenInUse로 사용하다가, 필요할 때 Always를 요청하는게 정석이다. 물론 Always를 바로 요청해도 되기는 하나, 그런 경우는 드물다.

Property Keys


info에 추가할 수 있는 키 리스트

NSLocationAlwaysAndWhenInUseUsageDescription: iOS 11 버전 이후 사용
NSLocationUsageDescription: macOS에서 사용
NSLocationWhenInUseUsageDescription: iOS 11 버전 이후 사용
NSLocationAlwaysUsageDescription: iOS 11 버전 이전 사용

위치 데이터 요청

  • gps, wifi, bluetooth등을 사용해서 위치 정보를 업데이트하고, 새로운 위치 정보가 업데이트되었다면 didUpdateLocations 델리게이트 메소드에서 locations 로 전달한다.
  • 해당 시점부터 배터리 소모가 심해지므로, 위치 정보가 더 이상 필요 없을 때, 에러가 발생했을 때 업데이트를 중지해야 한다.
    • 즉, didFailWithError에서 중지시켜줘야한다.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        //에러를 확인하려면 NSError타입으로 캐스팅해야함.
        let error = error as NSError
        guard error.code != CLError.Code.locationUnknown.rawValue else {return}
        
        print(error)
        manager.stopUpdatingLocation()
    }

메인 스레드에서 실행하면 블로킹

  • 이 메소드를 메인스레드에서 호출하면 스레드가 블로킹 된다.
  • 이 메소드를 호출하지 않아도 같은 기능을 구현 가능
  • DidChangeAuthorization에서 구현하고, 해당 메소드를 삭제하면 된다.
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .notDetermined:
            manager.requestWhenInUseAuthorization()
            break
        case .restricted, .denied:
            let alert = UIAlertController(title: "알림", message: "위치 서비스를 활성화시켜 주세요.", preferredStyle: .alert)
            
            let settingAction = UIAlertAction(title: "설정", style: .cancel){ _ in
                //string에 저장된 문자열로 url을 만든 다음에 open으로 전달하면 설정 앱으로 바로 이동 가능
                if let url = URL(string: UIApplication.openSettingsURLString){
                    UIApplication.shared.open(url)
                }
            }
            alert.addAction(settingAction)
            
            let cancelAction = UIAlertAction(title: "취소", style: .cancel)
            alert.addAction(cancelAction)
            
            present(alert, animated: true)
            break
        case .authorizedAlways, .authorizedWhenInUse:
            manager.startUpdatingLocation()
            break
        @unknown default:
            break
        }
    }

이벤트 처리 흐름

  1. CL매니저 생성과 함께 locationManagerDidChangeAuthorization(_:) -> .notDitermined
  2. 앱을 사용하는 동안 허용 선택 시 locationManagerDidChangeAuthorization(_:) -> .authorizedWhenInUse
  3. didUpdateLocations 호출

다시 앱을 시작할 경우
1. CL매니저 생성과 함께 locationManagerDidChangeAuthorization(_:) -> .authorizedWhenInUse
2. didUpdateLocations 호출

didUpdateLocations는 위치가 조금이라도 움직여도 호출되기 때문에

didUpdateLocations는 짧은 시간동안 여러번 호출된다는 것에 유의하자.

Setting Bundle

앱 권한에 연관된 설정 페이지로 바로 이동하는 방법

  • 없음
  • SettingBundel을 추가해야 함. 추가하기만 하면 됨.

  • 하단 3개의 설정 항목들이 Item 1,2,3 이다.
  • Identifier: User Default에서 key로 사용되어, 유저 디폴트에 접근하여 값을 읽고 쓸 수 있다.
    • 보통 설정을 구현할 때에는 앱 안에서 구현하기 때문에 Item들은 사용되지 않는다. (지워주자)
  • String Filename: 지역화에 사용되는 파일
    • 현재는 지역화가 관계 없으니 얘랑 함께 필요 없어서 지워도 됨.

이제는 안 뜬다.

profile
곰을 좋아합니다. <a href = "https://github.com/RudinP">github</a>

0개의 댓글