공식문서를 보면서 CoreLocation에대해서 한번 알아보겠습니다.
또, 프로젝트에서 실제 사용해보죠!
(참고한 공식문서 페이지는 맨 마지막에 참고로 올려두겠습니다 ☺️)
Core Location이란?
Core Location은 디바이스의 지정학적 위치, 고도, 방향 및 가까운 iBeacon 디바이스와 연관된 위치를 알려주는 서비스입니다.
(iBeacon - 근거리 무선 통신 기술을 바탕으로 신호를 발산하는 소형 장치)
Core Location 프레임워크는 Wi-Fi, 블루투스, 자력계, 기압계, 셀룰러 하드웨어 등 기기 내의 모든 요소들을 활용해서 데이터를 수집합니다.
Core Location은 CLLocationManager 클래스를 활용하며, 아래와 같은 활동들을 제공합니다.
- 표준적이고 중요한 위치 업데이트
- 구성가능한 정도의 정확도를 통하여 크고 작은 유저의 현재 위치 변화를 탐색합니다.
- 위치 모니터링
- 지정된 위치를 벗어났는 지 들어왔는 지를 판단합니다.
- 비콘 범위탐색
- 근처의 비콘 디바이스를 찾고, 해당 기기의 위치를 탐색합니다.
- 방향 찾기
- 기기 내에 있는 좌표계를 통하여 방향을 찾을 수 있습니다.
그리고, 위치 서비스를 활용하기 위해서는 먼저 위치 정보 수집 승인을 유저에게 받아야합니다. 그리고 이런 승인과 관련된 변화사항은 CLLocationManagerDelegate 프로토콜을 채택한 delegate 객체를 통하여 전달 받을 수 있습니다.
그러나, 아래와 같은 상황에서는 Core Location 서비스를 활용할 수 없습니다.
- Airplane mode일 때
- 필요한 하드웨어를 갖추고 있지 않을 때
- 디바이스가 Core Location의 특정 서비스를 지원하지 않을 때
- 앱이 사용자에게 위치 정보 수집 승인을 받지 못했을 때
Core Location Manager 객체 생성하기
위치 정보를 가져오는 데에는 시간이 걸리는 비동기 작업입니다. 따라서 CLLocationManagerDelegate을 채택한 Delegate 객체가 위치 정보를 받아오고, 에러 핸들링을 합니다. 구체적인 방법은 밑에서 알아보겠습니다.
만약 Custom Location Manager를 구현해본다면, 아래와 같이 구현해볼 수 있습니다.
class LocationDataManager : NSObject, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
}
// Location-related properties and delegate methods.
}
위치 정보 승인 받기
위치 승인 받기 전에, 우리는 locationManagerDidChangeAuthorization(_:) 호출을 하여 앱의 현재 승인 상태를 확인할 수 있습니다. 여기서 받아온 승인 상태를 토대로 다시 한번 승인을 요청할 것인 지를 판단할 수 있습니다.
그러면 위치 정보 승인 받는 과정을 자세하게 알아봅시다.
먼저 접근 레벨을 설정해야합니다. 접근 레벨은 두 가지가 존재합니다.
- When In Use
- 해당 레벨은 앱을 사용할 때만 위치 정보 수집을 허용하는 것입니다. 프라이버시 및 배터리 활용 측면에서 해당 레벨이 선호됩니다. 단, background에 간 상태에서도 위치 업데이트를 활용할 수 있습니다.(background 위치 정보 업데이트를 가능하게 하면)
- Always
- 앱이 아무때나 위치 정보를 업데이트할 수 있도록 설정합니다. 해당 레벨은 앱이 위치 변화에 따라서 푸시를 던지거나, 자동으로 위치 변화를 확인하여 time-sensitive한 반응을 던져야할 때 활용됩니다. 앱이 실행되지 않아도, 시스템이 해당 앱을 런치할 수 있습니다. (VisionOS에서는 사용 불가능이라고 하네요!)
자세한 내용은 해당 문서를 참고하면 좋을 거 같습니다!
(https://developer.apple.com/documentation/corelocation/handling_location_updates_in_the_background)
위와 같이 접근 레벨의 두 종류를 알아보았는데요.
시스템은 처음 승인 요청을 던질 때, 유저에게 승인을 할 것인지, 거절할 것인 지를 얼럿을 던져서 확인합니다.
얼럿 문구는 Info.plist를 통하여 설정할 수 있고, 아래는 얼럿 문구를 설정할 수 있는 Key들입니다.
NSLocationWhenInUseUsageDescription
(Privacy - Location Always and When In Use Usage Description)
- 앱은 When in Use 및 Always 승인 요청합니다.
NSLocationAlwaysAndWhenInUseUsageDescription
(Privacy - Location When In Use Usage Description)
- 앱은 Always 승인 요청합니다.
NSLocationUsageDescription
- macOS의 위치 정보 수집 승인 요청합니다.
앞서 현재 승인 레벨을 locationManagerDidChangeAuthorization을 통하여 확인할 수 있다고 했는데요.
해당 메소드는 설정에서 위치 정보 수집 레벨을 변경될 때에도 호출됩니다.
따라서, 해당 메소드에서 CLLocationManger의 authorizationStatus를 통하여 현재 승인 레벨을 확인하고, 그에 맞게 승인 요청을 해야합니다.
아래와 같이 구현할 수 있겠네요. (이해가 안되시더라도 맨 마지막에 가시면 이해가 되실겁니다!)
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse: // Location services are available.
enableLocationFeatures()
break
case .restricted, .denied: // Location services currently unavailable.
disableLocationFeatures()
break
case .notDetermined: // Authorization not determined yet.
manager.requestWhenInUseAuthorization()
break
default:
break
}
}
위의 코드에를 보면 만약 .notDetermined 상태라면 requestWhenInUseAuthorization() 메소드를 호출하죠?
그러면 requestWhenInUseAuthorization() 메소드에대해서 한번 알아봅시다.
requestWhenInUseAuthorization()
해당 메소드는 비동기적으로 실행이되며, 유저에게 승인 허락을 요청합니다. 즉 접근레벨 When In Use을 활용하여 위치 정보 수집을 요청합니다.
또한, NSLocationWhenInUseUsageDescription에 설정된 Text를 띄어주고,(위에서 말한 키입니다) 아래와 같은 옵션들을 선택할 수 있는 얼럿을 띄웁니다.
- Allow While Using App
- When In Use 승인이 만료되지 않습니다.
- Allow Once
- App이 사용되지 않을 때는 만료되는 단발성의 승인 요청 허락입니다.
- 앱이 사용되지 않을 때는 notDetermined 상태로 돌아갑니다.
- Don’t Allow
- 승인 요청을 거절합니다.
만약, 유저가 위의 옵션을 선택하면 locationManagerDidChangeAuthorization 메소드가 자동적으로 호출됩니다.
그렇다면, 위 메소드와는 조금 다른 requestAlwaysAuthorization() 메소드에대해서 알아보겠습니다.
requestAlwaysAuthorization()
앱이 사용되고 있는(in use)인 지 아닌 지 상관없이 위치 정보를 업데이트할 수 있도록 시스템에게 허락합니다.
NSLocationAlwaysAndWhenInUseUsageDescription와 NSLocationWhenInUseUsageDescription 값이 둘 다 info.plist에 들어가 있어야합니다. 또한 아래와 같은 옵션들을 선택할 수 있게 합니다.
- Not Determined
- When In Use
만약 앱이 해당 메소드를 호출하면, 그 이후의 호출은 무시가 됩니다. 즉, 해당 메소드 호출을 제한하는 것입니다.
(두 개가 각각 In Use일 때도 사용할 수 있게 하는 것과 그러지 못하게 막는 것이라는 차이점이 존재하다는 것은 알겠지만, 확실히 이해가 되지는 않습니다. 이후 이야기에서 이해를 할 수 있게 됩니다.)
requestAlwaysAuthorization()이 발동하는 두 가지 상황이 존재합니다.
- When In Use인 상태 (이전 requestWhenInUseAuthorization을 사용한 상태)
- When In Use가 아닌 상태(바로 requestAlwaysAuthorization을 호출한 상태)
일단 첫번째 상황에 대해서 이야기해보겠습니다.
1. When In Use인 상태 (이전 requestWhenInUseAuthorization을 사용한 상태)
만약 유저가 requestWhenInUseAuthorization()호출에서 Allow Once(한번만 허용)을 선택하면, 이후의 requestAlwaysAuthorization 메소드 호출은 무시하게됩니다.
그런데 만약 유저가 requestWhenInUseAuthorization() 호출 하여 When In Use를 승인하면, 시스템은 유저에게 Always permission을 요청합니다. 그리고, 아래와 같은 옵션을 선택할 수 있습니다.
- Keep Only While Using
- When In Use 레벨 단계로 유지하고, 더이상 업데이트를 받지 않습니다.
- Change to Always Allow
- Always 레벨 단계로 변경하고, 델리게이트 객체는 CLAuthorizationStatus.authorizedAlways 값을 받게됩니다.
2. When In Use가 아닌 상태(바로 requestAlwaysAuthorization을 호출한 상태)
그런데 만약, requestWhenInUseAuthorization()없이 requestAlwaysAuthorization() 혼자만 사용하게 되면 NSLocationWhenInUseUsageDescription과 연관된 String과 함께 아래 세 개의 옵션을 선택할 수 있게 됩니다.
- Allow While Using App
- 잠재적인 Always 레벨로 설정합니다. 그리고 Delegate 객체는 CLAuthorizationStatus.authorizedAlways 값을 받게 됩니다.
- Allow Once
- 일시적인 When In Use 레벨로 설정합니다. 델리게이트 객체는 CLAuthorizationStatus.authorizedWhenInUse 값을 받게 되고, 앱이 더 이상 사용되지 않을 때 (Not In Use)이면 CLAuthorizationStatus.notDetermined 값으로 되돌아 갑니다.
- Don’t Allow
- Denied 레벨로 설정합니다. 그리고 델리게이트 객체는 CLAuthorizationStatus.denied 값을 받게됩니다.
잠재적인 Always 레벨일 경우, 앱이 더이상 실행되지 않을 때(Not In Use), 얼럿을 띄어서 NSLocationAlwaysUsageDescription text와 함께 두 가지 옵션을 가지게 됩니다.
- Keep Only While Using
- When In Use 레벨로 설정합니다. 그리고, 델리게이트 객체는 CLAuthorizationStatus.authorizedWhenInUse 값을 받게 됩니다.
- Change to Always Allow
- 잠재적인 Always 레벨을 없애고, 영구적인 Always 단계로 변경시킵니다. 즉, 더이상 Delegate은 콜백을 받지 못합니다.
즉, 어떤 메소드를 호출하던 지 간에 처음 앱을 실행 후, Core Location을 활용하면 아래와 같은 얼럿을 받게됩니다.
만약 내가 requestWhenInUseAuthorization()를 호출한 거라면?
- Allow While In Use
- In Use일 때는 계속해서 위치 정보를 업데이트합니다.
- 다시 In Use가 될 때, 승인 여부를 물어보지 않습니다.
- Allow Once
- In Use일 때는 계속해서 위치 정보를 업데이트합니다.
- In Use가 아닐 때, notDetermined로 변경됩니다.
- 다시 In Use가 될 때, 승인 여부를 물어봅니다.
- Dont' Allow
- 그냥 승인하지 않습니다.
만약 내가 바로 .unDetermined에서 바로 requestAlwaysAuthorization()를 호출한 거라면?
- Allow While In Use
- 잠재적인 Always 상태가 됩니다. 즉, 시스템에서는 Always라고 하지만, 실제로 유저가 설정에서 확인해보면, when In Use 상태입니다.
- 그리고, 만약 not in use 상태가 되면 그 때 한번 물어봅니다. Not In Use일 때도 위치 정보를 업데이트할 것인지
- Allow Once
- In Use일 때는 계속해서 위치 정보를 업데이트합니다.
- In Use가 아닐 때, notDetermined로 변경됩니다.
- 다시 In Use가 될 때, 승인 여부를 물어봅니다.
- Dont' Allow
- 승인하지 않습니다.
iOS 12와 iOS 13의 차이점은 아래 그림으로 설명할 수 있습니다.
사용자가 무턱대로 그냥 위치 정보를 background에서도 줄 수있는 상황을 방지하고자, 애플에서는 not in use일 때, 승인 요청을 다시 한번 물어보는 것입니다. 그리고, 언제든지 다시 위치 정보 레벨을 조정할 수 있도록 구현한 것입니다.(Temporary, Provisional)
현재 위치 받아오기
이렇게 승인 요청을 했다면, 현재 위치를 받아와야겠죠?
위치 정보를 받아오는 것에도 여러가지 서비스가 존재합니다.
Visit location service
- 가장 에너지 효율적인 위치 정보 수집 방법입니다.
- 시스템은 해당 위치를 방문하고, 보낸 시간들을 모니터링하고 이후에 해당 데이터를 전달합니다.
- startMonitoringVisists()를 통하여 해당 서비스를 시작할 수 있습니다.
Significant-change service
- 가장 에너지가 적게드는 위치 정보 서비스입니다.
- 큰 위치 변화를 셀룰러나 Wi-Fi(GPS x)를 활용하여 전달합니다.
- startMonitoringSignificantLocationChanges() 메소드를 활용하여 해당 서비스를 시작할 수 있습니다.
Stand Location service
- 가장 정확하고, 규칙적인 위치 정보를 제공하는 서비스이지만, 큰 에너지가 필요합니다.
- 앱에서 단계별 네비게이션을 제공하거나, 높은 정확도가 필요한 경우에 사용합니다.
- VisionOS에서 유일하게 사용가능한 위치 서비스입니다.
- startUpdatingLocation()을 통하여 서비스를 시작하고 requestLocation()을 활용하여 단일 위치 이벤트를 받을 수 있습니다.
그리고, Core Location은 에너지 효율을 위하여 다음과 같은 옵션들을 설정할 수 있게 해두었습니다.
distanceFilter
- 위치 업데이트를 하기 전에 디바이스가 수직적으로 움직여야하는 최소 단위의 거리를 설정할 수 있습니다.
- Stand location service에서만 활용할 수 있습니다.
desiredAccuract
- 여러가지 옵션을 통하여 위치 정보 업데이트의 정확도를 설정할 수 있습니다.
- Default는 kCLLocationAccuracyBest입니다.
- Stand location service에서만 활용할 수 있습니다.
activityType
- 타입을 설정하여, 해당 타입에 맞게 유저가 사용을 하면 알아서 위치 정보 수집을 중단하거나 할 수 있는 선택지를 줍니다.
- CLActivityType.automotiveNavigation를 설정할 경우, 만약 유저의 위치가 변경되지 않을 경우, 시스템은 radio 하드웨어를 끄고, 더이상 새로운 위치를 탐색하지 않습니다.
프로젝트에 적용해보기
일단 저는 백그라운드에서 유저의 위치 정보를 받아올 일이 없기에 따로 Always 설정을 하지 않기로 했습니다.
아래와 같이 Info.plist를 구성할 수 있겠네요.
그리고, 아래와 같이 Custom Class를 구현해주었습니다.
(NSObject는 Core Location이 오브젝트-c와 연관되어 있어 상속받아야지만 Delegate을 채택할 수 있습니다.)
final class LocationManager: NSObject {
private var coreLocationManager = CLLocationManager()
override init() {
super.init()
coreLocationManager.delegate = self
coreLocationManager.requestWhenInUseAuthorization()
}
}
그리고, 아래와 같이 locationManagerDidChangeAuthorization(_:) 메소드를 구현했습니다.
- 만약 WhenInUse 상태라면 startUpdatingLocation() 메소드를 호출하여 Location을 받고
- 처음 앱을 띄우거나, 그 전에 Allow Once를 선택했을 경우, 다시 한번 승인 요청할 수 있도록 구현했습니다.
- 마지막으로 승인이 거부되면 alert를 통하여 사용자에게 앱의 위치 정보 업데이트 레벨을 설정해달라고 구현했습니다.
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse:
manager.startUpdatingLocation()
case .denied, .restricted:
// alert 구현
print("denied")
case .notDetermined:
manager.requestWhenInUseAuthorization()
default:
return
}
}
마지막으로 두 가지 메소드를 구현하여 현재 위치의 위도를 받아올 수 있도록 하였습니다.
- 정보를 받아올 때마다 locationManger(didUpDateLocations)가 호출됩니다.
- locationManger(didUpDateLocations)에서 에러가 생기면 locationManger(didFailWithError)가 호출됩니다.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
print(location.coordinate)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("didFailedWithError")
}
참고자료
https://developer.apple.com/documentation/corelocation/configuring_your_app_to_use_location_services
https://developer.apple.com/videos/play/wwdc2019/705/
What's New in Core Location - WWDC19 - Videos - Apple Developer
Location technologies are core to delivering context-based services within your app. Discover how the latest features in the Core...
developer.apple.com
'iOS' 카테고리의 다른 글
[iOS] CollectionView Reordering (feat. WWDC 20) (3) | 2023.09.15 |
---|---|
[iOS] Core Location Unit Test하기(feat. WWDC18) (0) | 2023.07.23 |
[iOS] delegate는 항상 weak var로 선언해야 될까? (0) | 2023.06.12 |
[iOS] Diffabledatasource의 identifier는 왜 Hashable 해야 할까? (0) | 2023.06.08 |
[iOS] FileManager (0) | 2023.06.08 |