2014년 11월 19일 수요일

iOS 8 이상에서 LocationManager 가 작동 하지 않는 경우

iOS 8 부터 또 새로운 보안 강화안이 마련된 것 같다. 이번 포스팅은 CLLocationManager를 이용하고자 할 때의 변동사항을 정리한다.

만약 기존 방식대로 CLLocationManager를 사용하는 경우, 혹은 별도의 코드는 없지만 인터페이스 빌더나 스토리보드에서 맵뷰(MKMapView)를 올려놓고 사용자 위치 탐색(Update User Location) 기능을 사용하는 경우 위치 탐색이 제대로 되지 않고 아래와 같은 로그메시지가 출력된다면 이 문제에 해당한다.
Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first.
사실 에러 내용만 보면 해결은 쉬울 것 같다. 안내해 주는 대로 코딩하면 될 것 같으니깐.

그런데 CLLocationManager 에서는 위 로그 메시지에 언급된 static method를 제공해 주지 않는다. (Swift 라면 class method) 물론 문서를 찾아보면 쉽게 답을 얻을 수는 있겠지만. :-)

iOS 8 부터는 Location Manager 사용 시 요구사항이 두 가지가 늘었다.

1. -Info.plist 수정

프로젝트를 생성하면 ~~~~info.plist 파일이 생성된다. 여기에 추가 키 등록이 필요하다. 키 이름은 아래 두 종류가 있다.
  • NSLocationWhenInUseUsageDescription: 앱이 활성화 되어 있는 도중에만 위치서비스를 이용하는 경우.
  • NSLocationAlwaysUsageDescription: 앱이 백그라운드에서도 위치서비스를 계속 이용해야 하는 경우.
원하는 키를 Add Row 기능을 이용해 추가하자. 타입은 String으로 될텐데 별 다른 값은 필요없다.

참고로 위 두가지 키를 다 추가해도 상관은 없다. 다만 아래 NSLocationAlwaysUsageDescription은 NSLocationWhenInUseUsageDescription을 포함하고 있다고 생각되니 상황에 맞게 하나만 골라도 무방할 듯 하다.

2. 코드 추가

CLLocationManager 인스턴스를 만들어서 사용하는 경우라면 디버그로그 내용 처럼 인증을 요청하는 코드를 넣기만 하면 된다. 없다면 인스턴스를 생성하는 코드를 넣어야 한다. 인스턴스를 생성하는 이유는 앞서 이야기 했지만 정적메소드가 아니기 때문이다. -_-;; 필요없더라도 만들어야 된다.

그리고 이 객체는 일단 앱이 실행되는 동안에는 계속 유지하도록 만들어야 한다. 따라서 AppDelegate의 프로퍼티로 등록해 두고 앱 라이프사이클 동안 살아있게 만들자. 로컬 변수에 이 CLLocationManager를 만들면 위치서비스 팝업이 뜨고 나서 바로 사라져버린다. ARC에 의해 소거당한 불운한 객체니까. -_-;

필요한 셀렉터는 iOS 8 부터 추가되었기 때문에 별도로 버전을 확인하는 것이 필요하다.
// Objective-C 코드
// CLLocationManager 인스턴스 생성(별도로 사용하는 경우라면 생략)
self.locationManager = [[CLLocationManager alloc] init];

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
    // 앱이 활성화 되어 있는 동안에만 위치서비스를 이용하는 경우
    [self.locationManager requestWhenInUseAuthorization];
    // 혹은 백그라운드에서도 위치서비스를 이용해야 하는 경우
    [self.locationManager requestAlwaysAuthorization];
}
굳이 써야 하나 생각되지만, Swift의 경우라면 거의 동일하게 쓰면 된다.
// Swift 코드
// CLLocationManager 인스턴스 생성(별도로 사용하는 경우라면 생략)
self.locationManager = CLLocationManager()

let versionString = UIDevice.currentDevice().systemVersion as NSString
let version = versionString.floatValue
if version >= 8.0 {
    // 앱이 활성화 되어 있는 동안에만 위치서비스를 이용하는 경우
    self.locationManager.requestWhenInUseAuthorization()
    // 혹은 백그라운드에서도 위치서비스를 이용해야 하는 경우
    self.locationManager.requestAlwaysAuthorization()
}
버전 체크 코드가 Objective-C 버전 보단 좀 귀찮은데 어쩔 수가 없다. Swift의 String은 toFloat() 같은 기능이 없다.

버전 체크 대신 respondsToSelector를 이용해 해당 셀렉터가 존재하는지 확인하고 호출하는 방법도 있지만 Swift 에서 쓰기엔 좀 곤란하니 그냥 버전을 체크하는 코드가 좀 더 엘레강트(?) 한 것 같다.

이렇게 하면 이제 앱이 실행될 때 사용자에게 위치서비스를 쓸 건지 물어오게 된다. 참고로 기존방식으로 이미 승낙한 경우에도 한 번은 더 물어보게 되는 것 같다.

댓글 없음 :