iOS는 사용자에게 Data Privacy
에 대해 더 많은 통제권을 주는 형태로 변화하고 있습니다. 간단한게 사용자의 위치를 사용하는 앱을 살펴보겠습니다.
어떻게 위의 그림처럼 사용자의 위치에 접근할 수 있을까요?
이는 사용자의 휴대폰의 설정에서 위치 서비스가 활성화 되어 있고, 앱에서 사용자의 위치에 접근할 수 있는 권한을 얻었기 때문입니다.
만약 사용자의 휴대폰에 위치서비스가 비활성화 되어 있다면, 위의 그림처럼 앱에서 사용자의 위치에 접근할 수 없습니다.
그리고, 사용자의 휴대폰에 위치서비스가 활성화 되어 있더라도, 앱에서 위치에 접근할 수 있는 권한이 없다면 위의 그림처럼 사용자의 위치에 접근할 수 없습니다.
❗️ iOS 13 부터 한번만 허용, iOS 14 정확한 위치 설정이 추가 되었습니다.
전체적인 권한 설정을 그림으로 나타내면 아래와 같습니다. (강의자료 내용입니다.)
위치권한을 이용해서 사용자의 위치를 지도에 띄어주는 예제를 확인해보겠습니다.
❗️ 위치권한이 없다고 해서 지도를 사용하지 못하는 것은 아닙니다.
우선, 앱에서 사용자의 위치 정보에 대한 액세스 요청하는 이유를 사용자에게 알려주는 메시지를 info.plist
를 통해 작성해야 합니다.
우리는 사용자가 앱을 사용할 때, 사용자의 위치에 접근할 수 있어야 하기 때문에 Location when in Use Usage Description
에 사용자에게 위치 접근 허용 하도록 유도하는 메시지를 작성합니다.
❗️ 우리가 사용하는 권한에 대해서만 사용해야 합니다. (리젝 사유가 됩니다.)
사용자의 위치 정보를 지도에 표현하기 위해서 Mapkit
을 사용합니다.
import Mapkit -> 위치를 지도에 표현
import CoreLocation -> 위치정보를 관리
let locationManager = CLLocaationManager()
locationManager.delegate = self
설정을 마치고, extension 을 통해서 CLLocationManager 에서 프로토콜로 설정된 메서드들을 구현합니다.
모든 케이스에 대한 설정이 필요합니다.
먼저, 사용자의 휴대폰에 위치 설정이 켜저있는가?
를 시작으로 분기점을 잡겠습니다.
// iOS 버전에 따른 분기 처리와 iOS 위치 서비스 여부 확인
func checkUserLocationServicesAuthorization() {
let authorizationStatus: CLAuthorizationStatus
if #available(iOS 14.0, *) {
authorizationStatus = locationManager.authorizationStatus //iOS 14 이상만 사용가능
}
else {
authorizationStatus = CLLocationManager.authorizationStatus() // iOS 14 미만
}
//iOS 위치 서비스 확인
if CLLocationManager.locationServicesEnabled() {
//권한 상태 확인 및 권한 요청가능 -> 8번 메서드 실행
checkCurrentLocationAuthorization(authorizationStatus)
}
//아예 시스템에서 위치 기능을 끈 경우
else {
print("iOS 위치 서비스를 켜주세요!")
}
}
우선, authorizationStatus
는 앱 내에서의 권한을 의미합니다.
iOS 14 이상에서 위치에 대한 권한은 정확도 설정
이 추가되었기 때문에 추가적인 선택옵션이 존재하므로 따로 설정이 필요합니다. (열거형에 선택 조건이 추가된 형태?)
그리고 현재 사용자의 휴대폰에서 위치 설정이 켜져 있다면, 현재 앱 내에서 위치 권한을 확인하는 함수로 가게 됩니다.
해당 함수로 넘어가서 현재 권한 상태에 따라 분기점이 또 나뉘게 됩니다.
그리고 만약 사용자의 휴대폰에서 위치 설정이 꺼져 있다면, 앱 내에서 위치 권한을 요청할 수 없기 때문에 print 구문을 출력하고 종료됩니다.
Alert 을 띄워서 위치서비스를 활성화 해달라고 하는 첫번째 예제 사진 상황입니다.
그럼, 사용자의 위치 설정이 켜저있어서, 현재 앱 내에서의 위치 권한을 확인하는 함수로 넘어왔을 때를 살펴보겠습니다.
func checkCurrentLocationAuthorization(_ authorizationStatus: CLAuthorizationStatus) {
switch authorizationStatus {
case .notDetermined:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
// 앱을 사용하는 동안에 대한 위치 권환 요청
locationManager.requestWhenInUseAuthorization()
// 위치 접근 시작! (같이 나와야함)
locationManager.startUpdatingLocation()
case .restricted, .denied:
print("Denied, 설정으로 유도")
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
case .authorizedAlways:
print("Always")
@unknown default:
print("Default")
}
if #available(iOS 14.0, *) {
//정확토 체크: 정확도 감소가 되어 있을 경우, 1시간 4번으로 제한
let accurancyState = locationManager.accuracyAuthorization
switch accurancyState {
case .fullAccuracy:
print("Full")
case .reducedAccuracy:
print("Reduce")
@unknown default:
print("Default")
}
}
}
앱 내에서 위치 정보 권한에 대한 분기점은 크게
(다양한 case)
iOS 14 이상의 버전에서는 위치의 정확도에 대한 옵션도 있기 때문에 추가적인 설정을 하는 것을 확인할 수 있습니다.
만약 앱 내에서 위치 정보 권한이 거절되어 있는 경우에는 Alert 을 띄워서 사용자에게 허용을 할 수 있는 설정창으로 넘어가는 것이 일반적입니다
그리고, 사용자가 위치 정보를 허용한 경우에는 startUpdatingLocation
을 통해서 위치 접근을 시작
해당 부분 동작은 다시 한번 확인해보겠습니다.
// 4) 사용자가 앱 내에서 위치 허용을 한 경우,
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("감사합니다. 허용해 주셔서")
// 사용자가 앱 내에서 위치 허용을 해줬으니까 현재 위치를 지도의 중심으로 설정한다.
if let coordinate = locations.last?.coordinate {
let annotation = MKPointAnnotation()
annotation.title = "나 여기"
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: coordinate, span: span)
mapView.setRegion(region, animated: true)
//10. 중요!!
locationManager.stopUpdatingLocation()
}
//얘기치 못한 오류 방지 (비행기 모드로 변환되었을 경우) <-> 5번함수와의 기능
else {
print("Location Cannot Find")
}
}
사용자가 앱 내에서 위치권한을 허용한 경우, 위치를 받아서 지도상에 annotation
을 찍어 주도록 구현했습니다.
이때도, 위치권한은 허용되어 있지만 위치 정보를 가져오지 못하는 예외 경우에 대해서도 처리를 해주어야 합니다!
stopUpdatingLocation() 이 구현되어 있으면, 앱을 킬 때 위치가 계속해서 수정되지 않고 고정되어 있습니다.
실제 기기에 앱을 넣고 테스트한 결과, 위치가 변경되지 않았습니다.
해당 메서드를 빼고 테스트 하고 다시 결과를 올리겠습니다.
// 5) 위치 접근에 실패한 경우(여기에 사용자가 앱내 위치 정보를 거부한것도 포함되는건가?)
// 위치접근을 허용했으나 위치 정보 조회에 실패한 경우 지도에서 어떤화면을 보여줄 것인가?
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("허용 안했어? 왜 오류가 나!")
//위치 허용하지 않은 경우, 건국대 근처 스터디 카페
let location = CLLocationCoordinate2D(latitude: 37.541366, longitude: 127.067187)
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
let annotation = MKPointAnnotation()
annotation.title = "눈물의 스터디 현장"
annotation.coordinate = location
mapView.addAnnotation(annotation)
}
사용자의 위치 정보 접근에 실패한 경우, 위의 메서드가 호출됩니다.
허용했으나 오류가 나는 경우, 위치 허용을 하지 않은 경우 모두 해당 메서드가 호출되는건지 정확하게 모르겠습니다. (그럼 위에서 else 로 분기처리한것과 어떤차이가?..)
// 6) iOS 14 이상
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print("승인상태 변경")
checkUserLocationServicesAuthorization()
}
// 7) iOS 14 미만
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("승인상태 변경")
checkUserLocationServicesAuthorization()
}
권한 상태가 변경된 경우에는, 위의 메서드가 실행됩니다.
즉, 권한 상태가 변경되면 위치 권한을 다시 확인 하게 됩니다.
어떻게 변경되는것을 알 수 있을까요? 인터럽트 처럼 설정되어 있는걸까요?
추후에 정확한 동작 원리를 알게되면 정리하도록 하겠습니다.
복잡합니다... 하지만 천천히 보면 이해할 수 있습니다.
아직까지는 아리송(?) 한 부분들이 있지만 다른 예제들을 다뤄 보면서 알게되면 다시 정리하는 시간을 가질 수 있도록 하겠습니다!