애플에서는 위치 관련 서비스를 위한 Core Location 이라는 프레임워크를 제공합니다.
Core Location 프레임워크를 사용하여 사용자의 위치 데이터를 받고 처리하는 방법을 알아보겠습니다.
Core Location이란?
Core Location을 사용하면 디바이스의 내장 수신 장치들을 활용하여 위치 데이터를 수집하고 처리할 수 있습니다.
CLLocationManager
Core Location 서비스를 구성-시작-종료 하기 위해서는 CLLocationManager 클래스 인스턴스를 사용해줍니다.
CLLocationManager는 아래와 같은 기능들을 지원하는데, 이번 글에서 주로 필요한 내용은 위치 변화 추적(Standard and significant location updates)이 되겠습니다.
- 위치 변화 추적(Standard and significant location updates)
- 지역 모니터링(Region monitoring)
- 비콘 감지(Beacon ranging)
- 나침반 관련 처리(Compass headlings)
권한 설정
Core Location에서 제공하는 위치 서비스 기능을 사용하려면 권한 설정이 필요합니다.
📌 Info.plist에 usage description 작성
인증 요청 시 메세지에 표시할 내용을 작성해줍니다. Info.plist에 관련 key를 추가하여, string value로 앱이 위치 데이터에 액세스하려는 이유를 설명하는 내용을 작성해줍니다.
위치 데이터의 접근 수준에 따라 서로 다른 key를 설정해주어야 합니다.
- Privacy - Location When In Use Usage Description : When in Use / Always 권한 모두 필요
- Privacy - Location Always and When In Use Usage Description : Always일 때 필요
- Privacy - Location Usage Description : macOS 앱일 경우 필요
Core Location은 2가지 접근 수준을 지원합니다. Always의 경우에는 내비게이션 같은 앱이 아닌 이상 배터리 효율을 위해 사용하지 않는 것이 좋습니다. 저는 When in Use 수준으로 사용할 것이기 때문에 Info.plist의 key로 Privacy - Location When In Use Usage Description만 추가해주었습니다.
- When in Use : 앱을 사용할 때만 위치 업데이트를 사용
- Always : 항상 위치 업데이트 사용 가능 (백그라운드에서도)
📌 위치 서비스 권한 요청하기
실제 앱에서 코드로 권한을 요청하려면 앱의 현재 인증 상태를 확인하고, 필요한 경우에 인증 요청을 하는 것이 좋습니다.
CLLocationManager 객체의 authorizationStatus 프로퍼티를 통해 현재 앱의 인증 상태를 받아올 수 있고,
CLLocationManagerDelegate 프로토콜을 채택한 location manager 객체에서 locationManagerDidChangeAuthorization(_:) 메서드를 통해 인증 상태에 따른 처리를 할 수 있습니다. 해당 메서드는 앱이 location manager 객체를 생성했을 때와 인증 상태가 변경될 때마다 호출됩니다.
(코드 기반의 UIKit으로 작성된 코드입니다.)
뷰 컨트롤러에서 CLLocationManager 객체를 생성하고, 뷰 컨트롤러를 델리게이트로 지정해줍니다.
그리고 뷰 컨트롤에서 CLLocationManagerDelegate 프로토콜을 채택하고, locationManagerDidChangeAuthorization(_:) 메서드를 구현하여 인증 상태에 따른 처리 코드를 작성해줍니다.
When in Use 레벨의 승인을 요청할 때는 locationManager.requestWhenInUseAuthorization() 를 사용해 요청할 수 있습니다. (Always 레벨이라면 requestAlwaysAuthorization() 이겠죠?)
import CoreLocation
public final class MainViewController: UIViewController {
...
let locationManager: CLLocationManager
init() {
print("locationManager 인스턴스 생성!!")
locationManager = CLLocationManager()
super.init(nibName: nil, bundle: nil)
}
...
public override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
}
...
}
extension MainViewController: CLLocationManagerDelegate {
public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// CLLocationManager 객체의 athorizationStatus 프로퍼티를 통해 앱의 현재 인증 상태를 확인 가능
switch manager.authorizationStatus {
case .authorizedWhenInUse: // 위치 서비스를 사용 가능한 상태
print("위치 서비스 사용 가능")
break
case .restricted, .denied: // 위치 서비스를 사용 가능하지 않은 상태
print("위치 서비스 사용 불가")
break
case .notDetermined: // 권한 설정이 되어 있지 않은 상태
print("권한 설정 필요")
locationManager.requestWhenInUseAuthorization() // 권한 요청
break
default:
break
}
}
}
위에서 설명했다시피 locationManagerDidChangeAuthorization(_:) 메서드는 location manager 객체가 생성되었을 때와 인증 상태가 변경될 때마다 호출됩니다.
그렇기 때문에 프로젝트를 실행하기 되면 해당 뷰 컨트롤러가 초기화될 때 CLLocationManager 클래스의 인스턴스인 locationManager 객체도 초기화되면서 델리게이트 메서드를 호출하여 인증 상태를 한 번 확인하게 됩니다. 이 때는 아직 권한 설정이 되어 있지 않기 때문에 "권한 설정 필요" 라는 메세지가 출력될 것입니다.
그 후 권한 요청 팝업이 뜨고, 권한을 설정하게 된다면 다시 한 번 델리게이트 메서드가 호출되며 설정된 인증 상태를 확인하게 됩니다. 만약 승인을 했다면 "위치 서비스 사용 가능" 이라고 메세지가 출력될 것입니다.
현재 유저의 위치 받아오기
이제 위치 데이터에 대한 접근 권한을 받았으니 위치 데이터를 활용하는 법을 알아보겠습니다.
Core Location은 여러 종류의 위치 서비스를 제공하는데, 앱에서 위치 데이터의 활용 용도와 배터리 효율을 고려하여 CLLocationManager 객체가 적절한 메서드를 호출하도록 구성해주어야 합니다.
CLLocationManager 객체에서 제공하는 Standard location service(표준 위치 서비스)와 관련된 메서드&프로퍼티는 아래와 같습니다.
- func startUpdatingLocation() : 유저의 현재 위치를 보고하는 업데이트를 시작 (추적 시작)
- func stopUpdatingLocation() : 위치 업데이트를 중단
- func requestLocation() : 유저의 현재 위치를 1번 요청
- var pausesLocationUpdatesAutomatically: Bool : location manager 객체가 위치 업데이트를 직접 일시 중지 할 수 있는지에 대한 프로퍼티
- var allowsBackgroundLocationUpdates: Bool : 앱이 백그라운드에서도 위치 업데이트를 받을 수 있는지에 대한 프로퍼티
- var showsBackgroundLocationIndicator: Bool : 앱이 백그라운드에서 위치 서비스를 사용하고 있을 때 상단 status 바에 표시할지 말지
- var activityType: CLActivityType : 앱이 앱의 위치 세션에서 사용자가 일반적으로 수행할 것으로 기대하는 활동 유형.
- enum CLActivityType : 위치 업데이트와 관련된 활동 유형
이번 포스팅에서는 간단하게 버튼을 누르면 현재 위치를 1번 받아와서 화면에 표시해주도록 구현해보려고 합니다. 그렇게 하기 위해서는 이 중에서 requestLocation() 메서드를 활용할 수 있습니다.
📌 requeestLocation() 메서드
CLLocationManager 객체가 requestLocation() 메서드를 호출하면, 델리게이트 객체의 locationManager(_:didUpdateLocations:) 메서드를 통해 위치 데이터를 받아올 수 있습니다.
⚠️ requestLocation() 메서드 사용 시에는 아래 2가지 델리게이트 메서드를 반드시 구현해주어야 합니다.
- locationManager(_:didUpdateLocations:)
- locationManager(_:didFailWithError:)
화면의 버튼을 클릭했을 때 locationManager가 requestLocation()를 호출하고, 델리게이트 메서드에서는 위치 데이터를 받아서 화면에 표시하도록 코드를 작성해 보았습니다.
public final class MainViewController: UIViewController {
...
public override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
customView.locationButton.addTarget(self, action: #selector(loadUserLocation), for: .touchUpInside)
}
...
@objc func loadUserLocation() {
locationManager.requestLocation()
}
}
extension MainViewController: CLLocationManagerDelegate {
...
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let coordinate = locations.last?.coordinate {
customView.locationLabel.text = "현재 위치 : \(coordinate.latitude), \(coordinate.longitude)"
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("위치 정보를 받아오는 데 실패함")
}
}
프로젝트를 빌드하면 아래와 같이 동작하게 됩니다.
전체 코드
ViewController
import UIKit
import CoreLocation
public final class MainViewController: UIViewController {
let customView = MainView()
let locationManager: CLLocationManager
init() {
print("locationManager 인스턴스 생성!!")
locationManager = CLLocationManager()
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
customView.locationButton.addTarget(self, action: #selector(loadUserLocation), for: .touchUpInside)
}
public override func loadView() {
view = customView
}
@objc func loadUserLocation() {
locationManager.requestLocation()
}
}
extension MainViewController: CLLocationManagerDelegate {
public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// CLLocationManager 객체의 athorizationStatus 프로퍼티를 통해 앱의 현재 인증 상태를 확인 가능
switch manager.authorizationStatus {
case .authorizedWhenInUse: // 위치 서비스를 사용 가능한 상태
print("위치 서비스 사용 가능")
break
case .restricted, .denied: // 위치 서비스를 사용 가능하지 않은 상태
print("위치 서비스 사용 불가")
break
case .notDetermined: // 권한 설정이 되어 있지 않은 상태
print("권한 설정 필요")
locationManager.requestWhenInUseAuthorization() // 권한 요청
break
default:
break
}
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let coordinate = locations.last?.coordinate {
customView.locationLabel.text = "현재 위치 : \(coordinate.latitude), \(coordinate.longitude)"
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("위치 정보를 받아오는 데 실패함")
}
}
View
import UIKit
import SnapKit
public final class MainView: UIView {
lazy var locationLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.text = "현재 위치 : "
return label
}()
lazy var locationButton: UIButton = {
let config = UIButton.Configuration.filled()
let button = UIButton(configuration: config)
button.setTitle("현재 위치 받아오기", for: .normal)
return button
}()
init() {
super.init(frame: .zero)
setUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setUI() {
self.backgroundColor = .white
addSubview(locationLabel)
locationLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
addSubview(locationButton)
locationButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(locationLabel.snp.bottom).offset(50)
}
}
}
Core Location의 공식 문서를 읽고 노션에 정리한 링크입니다.
https://healthy-degree-cc2.notion.site/Core-Location-a433acae3797473fba627f49f324d326?pvs=4
참고
https://developer.apple.com/documentation/corelocation
https://developer.apple.com/documentation/corelocation/configuring_your_app_to_use_location_services
https://developer.apple.com/documentation/corelocation/getting_the_current_location_of_a_device
https://developer.apple.com/documentation/corelocation/cllocationmanager
'iOS' 카테고리의 다른 글
[iOS] Build input file cannot be found: '~/Info.plist' 에러 발생 시 (0) | 2024.05.05 |
---|---|
[iOS] Core Location 테스트하기 (외부 의존성의 응답을 테스트하기) (0) | 2024.05.03 |
[iOS] 외국 밈(meme)에서 많이 쓰는 폰트 적용해보기 (SwiftUI) (0) | 2024.04.18 |
[iOS] M1 맥북 + Xcode 15 환경에서 SwiftLint 적용 시 오류 처리 (0) | 2024.02.28 |
[ iOS ] CustomStringConvertible 프로토콜 (0) | 2024.02.13 |