SwiftUI

SwiftUI - Core Location 프레임워크

iosos 2024. 3. 3. 20:55

Core Location

  • iOS 및 macOS 앱에서 위치 관련 서비스를 활용할 수 있는 프레임워크
  • iPhone Device의 위치 정보를 얻을 수 있는 기능을 제공하는 프레임워크 (지리적 위치, 고도 및 방향)
  • 이를 통해 위치 데이터를 쉽게 얻을 수 있으며 사용자의 위치, 이동 경로, 근접 지점 등 다양한 기능 구현 가능
  • Wi-fi, GPS, Bluetooth 등을 이용하여 정보를 모음

위치 정보를 사용하기 이전 꼭 사용자에게 위치 정보를 사용할건지 물어야 됨!

→ Info.plist에서 위치 권한 허용 (필수 작업)

  • Key 값을 Info.plist에 추가

 

 

 

 

1. CLLocationManger

  • 필수 사용 클래스
  • 위치 관리자 객체로, 위치 데이터를 수집하고 관리하는 핵심 클래스
  • 위치 관리자는 사용자의 위치 업데이트 요청 및 업데이트를 받으면 델리게이트에게 알리는 역할을 함
import CoreLocation

let locationManager = CLLocationManager() // locationManager 인스턴스 생성
locationManager.delegate = self  // delegate 설정
locationManager.requestWhenInUseAuthorization() // 앱을 사용할 때만 위치 정보를 허용할 경우 호출
locationManager.startUpdatingLocation()  // 위치 정보를 지속적으로 받고 싶은 경우 이벤트 시작

 

기능

  1. 위치 업데이트 요청
  2. 권한 요청 및 확인
    • 위치 서비스에 엑세스하기 위해서는 앱이 사용자의 권환을 요청해야 함
    locationManager.requestWhenInUseAuthorization() // 앱을 사용할 때만 위치 정보 허용
    locationManager.requestAlwaysAuthorization() // 항상 위치 정보 허용
  3. 정확도 설정
    • 정확도가 높을수록 더 많은 배터리 소모
    locationManager.desiredAccuracy = kCLLocationAccuracyBest // default 값
    
    /*
    kCLLocatoinAccuracyBestForNavigation - 가장 높은 수준의 정확도, 외부 전원이 연결되어 있을 경우에만 사용
    kCLLocatoinAccuracyBest - 베터리로 동작할 때 권장되는 가장 높은 수준의 정확도
    kCLLocatoinAccuracyNearestTenMeters - 10미터 이내의 정확도
    kCLLocatoinAccuracyHundredMeters - 100미터 이내의 정확도
    kCLLocatoinAccuracyKilometer - 1키로미터 이내의 정확도
    kCLLocatoinAccuracyThreeKilometers - 3키로미터 이내의 정확도
    */
  4. 백그라운드에서의 위치 업데이트
    • 백그라운드에서 위치 기반 서비스 실행 가능
    locationManager.allowsBackgroundLocationUpdates = true
  5. 모니터링 지역 등록
    • 특정 지역을 지정하고 해당 지역에 들어가거나 나오는 이벤트 감지 가능
    let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), radius: 100, identifier: "SanFrancisco")
    locationManager.startMonitoring(for: region)
  6. CLLocationManagerDelegate 사용
    • CLLocationManagerDelegate 프로토콜을 채택하여 위치 업데이트 및 에러 처리 관련 이벤트 다룸
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // 위치 업데이트 처리
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        // 에러 처리
    }

 

 

2. CLLocation

  • 위치 정보를 나타내는 클래스
  • 위도(latitude), 경도(longitude), 고도(altitude), 정확도의 속성을 가짐
  • 위치가 변경될 때마다 호출되며 가장 최근 위치 데이터를 배열의 마지막 객체에 포함하는 CLLocation 객체들의 배열이 인자로 전달됨
  	// 위치 정보 계속 업데이트 -> 위도 경도 받아옴
    // 위치가 변경될 때마다 호출되며 가장 최근 위치 데이터를 배열의 마지막 객체에 포함하는 CLLocation 객체들의 배열이 인자로 전달됨
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        print("didUpdaterLocation")
        if let location = locations.last {
            print("위도 : \\(location.coordinate.latitude)")
            print("경도 : \\(location.coordinate.longitude)")
            print("고도(m 단위) : \\(location.altitude)")
            print("수평 정확도(m 단위) : \\(location.verticalAccuracy)")
            print("수직 정확도(m 단위) : \\(location.horizontalAccuracy)")
        }
    }

 

 

3. CLGeocoder

  • 지오코딩 서비스를 제공하는 클래스
  • 주소 문자열과 좌표 간 변환 수행
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
    if let placemark = placemarks?.first {
        let address = placemark.name
        let city = placemark.locality
        // 주소 정보 사용
    }
}

 

 


위치 정보 받아오기 

여러 권한 중 위치 정보를 받아오기 위해서는 다음과 같은 순서로 작업하면 됨

  1. info.plist에 필요한 권한 추가 & Description 작성
  2. import CoreLocation & CLLocation Manager 인스턴스 생성
  3. 사용자 디바이스의 위치 서비스가 활성화 상태인지 확인하는 메서드 추가
  4. 앱에 대한 위치 권한이 부여된 상태인지 확인하는 메서드 추가
  5. Delegate설정 & Delegate Protocol 채택

 

 

 

1. Info.plist에서 위치 권한 허용 (필수 작업)

Value : 권한이 필요한 사유

 

 

 

 

2. import CoreLocation & CLLocation Manager 인스턴스 생성

import CoreLocation

class CoreLocationEx : UIViewController {
    
    // CLLocationManager 인스턴스 생성
    var locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
	}
}

 

 

 

 

3. 사용자 디바이스의 위치 서비스가 활성화 상태인지 확인하는 메서드 추가

3-1. 디바이스 자체에 위치 서비스가 활성화 상태인지 확인

  • CLLocationManager의 타입 메서드인 locationServicesEnabled() 메서드를 통해 활성화 상태 여부를 Bool 값으로 반환 함
  • 만약 사용자 디바이스의 위치 서비스가 비활성화 상태인 경우, 위치에 대한 권한 요청 자체가 불가하기 때문에 시스템 설정에서 사용자가 직접 설정 값을 변경하도록 유도해야 함

3-2. 사용자 디바이스의 위치 서비스가 활성화 상태라면, 앱에 대한 권한 상태 확인

  • 앱에 대한 권한 상태는 CLAuthorizationStatus라는 열거형 타입으로 표현됨
  • iOS 14를 기준으로 코드가 변경되어 분기 처리 필요
func checkUserDeviceLocationServiceAuthorization() {
        
    // 3.1
    guard CLLocationManager.locationServicesEnabled() else {
    	// 시스템 설정으로 유도하는 커스텀 얼럿
        showRequestLocationServiceAlert()
        return
    }
        
        
    // 3.2
    let authorizationStatus: CLAuthorizationStatus
        
    // 앱의 권한 상태 가져오는 코드 (iOS 버전에 따라 분기처리)
    if #available(iOS 14.0, *) {
        authorizationStatus = locationManager.authorizationStatus
    }else {
        authorizationStatus = CLLocationManager.authorizationStatus()
    }
        
    // 권한 상태값에 따라 분기처리를 수행하는 메서드 실행
    checkUserCurrentLocationAuthorization(authorizationStatus)
}

 

 

 

 

4. 앱에 대한 위치 권한이 부여된 상태인지 확인하는 메서드 추가

  • 사용자가 앱에 설정한 권한 상태를 매개변수로 입력받고 분기 처리하는 메서드 생성 (3번에서 작성한 메서드에서 호출)
  • CLAuthorizationStatus (권한 상태를 나타내는 열거형 타입)
    • .notDetermined : 사용자가 권한에 대한 설정을 선택하지 않은 상태
    • .restricted : 위치 서비스에 대한 관한이 없는 상태 / 자녀 보호 기능과 같은 상황으로 디바이스 자체에 활성이 제한된 상태
    • .denined
      • 사용자가 앱에 대한 권한을 거부한 상태
      • 권한을 승인했더라도 추후에 시스템 설정에서 비활성화 한 경우
      • 사용자가 디바이스 전체에 대한 위치 서비스를 비활성화 한 경우
      • 비행기 모드와 같은 상황으로, 위치 서비스를 이용할 수 없는 상황
    • .authorizedAlways : 앱이 백그라운드 상태에서도 위치 서비스를 이용할 수 있도록 승인된 상태
    • .authorizedWhenInUse : 앱이 포그라운드 상태에서만 위치 서비스를 이용할 수 있도록 승인된 상태
  • desiredAccuracy (위치 데이터의 정확도 설정)
    • kCLLocatoinAccuracyBestForNavigation - 가장 높은 수준의 정확도, 외부 전원이 연결되어 있을 경우에만 사용
    • kCLLocatoinAccuracyBest - 베터리로 동작할 때 권장되는 가장 높은 수준의 정확도
    • kCLLocatoinAccuracyNearestTenMeters - 10미터 이내의 정확도
    • kCLLocatoinAccuracyHundredMeters - 100미터 이내의 정확도
    • kCLLocatoinAccuracyKilometer - 1키로미터 이내의 정확도
    • kCLLocatoinAccuracyThreeKilometers - 3키로미터 이내의 정확도
func checkUserCurrentLocationAuthorization(_ status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        // 사용자가 권한에 대한 설정을 선택하지 않은 상태
        
        // 권한 요청을 보내기 전에 desiredAccuracy 설정 필요
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        
        // 권한 요청을 보낸다.
        locationManager.requestWhenInUseAuthorization()
            
    case .denied, .restricted:
    	// 사용자가 명시적으로 권한을 거부했거나, 위치 서비스 활성화가 제한된 상태
        // 시스템 설정에서 설정값을 변경하도록 유도한다.
        // 시스템 설정으로 유도하는 커스텀 얼럿
        showRequestLocationServiceAlert()
        
    case .authorizedWhenInUse:
    	// 앱을 사용중일 때, 위치 서비스를 이용할 수 있는 상태
        // manager 인스턴스를 사용하여 사용자의 위치를 가져온다.
        locationManager.startUpdatingLocation()
        
    default:
        print("Default")
    }
}

 

 

 

사용자의 현재 위치를 가져오는 2가지 방법

  1. requestLocation() : 한 번만 위치를 요청
  2. startUpdatingLocation() : 현재 위치를 지속적으로 요청
    • 더 이상 지속적으로 받아올 필요 없다면 stopUpdatingLocation() 사용하여 멈춤

 

 

 

5. Delegate설정 & Delegate Protocol 채택

5-1. 2단계에서 생성한 CLLocationManager의 인스턴스에 delegate 설정

class ViewController: UIViewController {

    let locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        locationManager.delegate = self
    }
}

 

 

5-2. CLLocationManagerDelegate 프로토콜을 채택하고, Delegate 메서드를 구현

CLLocationManagerDelegate Methods

  • 사용자의 위치를 성공적으로 가져왔을 때 호출 : didUpdateLocations
  • 사용자의 위치를 가져오지 못한 경우 호출 : didFailWithError
  • 앱에 대한 권한 설정이 변경되면 호출
    • iOS 14 이상 ) locationManagerDidChangeAuthorization
    • iOS 14 미만 ) didChangeAuthorization
extension ViewController: CLLocationManagerDelegate {
    
    // 사용자의 위치를 성공적으로 가져왔을 때 호출
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        // 위치 정보를 배열로 입력받는데, 마지막 index값이 가장 정확하다고 한다.
        if let coordinate = locations.last?.coordinate {
            // ⭐️ 사용자 위치 정보 사용
        }
        
        // startUpdatingLocation()을 사용하여 사용자 위치를 가져왔다면
        // 불필요한 업데이트를 방지하기 위해 stopUpdatingLocation을 호출
        locationManager.stopUpdatingLocation()
    }
    
    
    
    
    // 사용자가 GPS 사용이 불가한 지역에 있는 등 위치 정보를 가져오지 못했을 때 호출
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(#function)
    }
    
    
    
    
    // 앱에 대한 권한 설정이 변경되면 호출 (iOS 14 이상)
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // 사용자 디바이스의 위치 서비스가 활성화 상태인지 확인하는 메서드 호출
        checkUserDeviceLocationServiceAuthorization()
    }
    
    // 앱에 대한 권한 설정이 변경되면 호출 (iOS 14 미만)
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        // 사용자 디바이스의 위치 서비스가 활성화 상태인지 확인하는 메서드 호출
        checkUserDeviceLocationServiceAuthorization()
    }
}

 

 

 

 

디바이스의 시스템 설정으로 유도하는 커스텀 alert

func showRequestLocationServiceAlert() {
    let requestLocationServiceAlert = UIAlertController(title: "위치 정보 이용", message: "위치 서비스를 사용할 수 없습니다.\\n디바이스의 '설정 > 개인정보 보호'에서 위치 서비스를 켜주세요.", preferredStyle: .alert)
    let goSetting = UIAlertAction(title: "설정으로 이동", style: .destructive) { _ in
        if let appSetting = URL(string: UIApplication.openSettingsURLString) {
            UIApplication.shared.open(appSetting)
        }
    }
    let cancel = UIAlertAction(title: "취소", style: .default) { [weak self] _ in
        async { await self?.reloadData() }
    }
    requestLocationServiceAlert.addAction(cancel)
    requestLocationServiceAlert.addAction(goSetting)
    
    present(requestLocationServiceAlert, animated: true)
}

 

 


SwiftUI 적용 

1. 클래스 선언 및 프로퍼티 초기화

class CoreLocationEx: NSObject, ObservableObject, CLLocationManagerDelegate {
    @Published var latitude: Double = 0.0
    @Published var longitude: Double = 0.0
    @Published var altitude: Double = 0.0

    var locationManager = CLLocationManager()

    override init() {
        super.init()
        self.setupLocationManager()
    }
  • 위도, 경도, 고도가 변경될 때마다 화면과 연결하기 위해 @Published 속성 사용
  • 초기화 메서드에서 ‘setupLocationManager’ 메서드를 호출하여 locationManager의 설정 초기화
  • NSObject 는 init을 위해 사용

 

2. setupLocationManager 메서드

func setupLocationManager() {
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestWhenInUseAuthorization()

    if CLLocationManager.locationServicesEnabled() {
        print("위치 서비스 On 상태")
        locationManager.startUpdatingLocation()
        print(locationManager.location?.coordinate as Any)
    } else {
        print("위치 서비스 Off 상태")
    }
}
  • locationManager의 delegate를 현재 클래스로 설정
  • locationManager.desiredAccuracy = kCLLocationAccuracyBest : 위치 정확도를 kCLLocationAccuracyBest로 설정
  • locationManager.requestWhenInUseAuthorization() : 위치 서비스를 사용하기 위한 권한 요청
  • 위치 서비스가 활성화 되어있으면 위치 업데이트 시작, 현재 위치 좌표 출력

 

 

3. locationManager(_ : didUpdateLocations: ) 메서드

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let location = locations.last {
        latitude = location.coordinate.latitude
        longitude = location.coordinate.longitude
        altitude = location.altitude
    }
}
  • 위치 정보가 업데이트 될 때 호출되는 메서드
  • 위도, 경도, 고도 업데이트

 

 

4. locationManagerDidChangeAuthorization 및 관련 메서드

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    DispatchQueue.global().async {
        self.checkUserDeviceLocationServiceAuthorization()
    }
}

func checkUserDeviceLocationServiceAuthorization() {
    // ...
}

func checkUserCurrentLocationAuthorization(_ status: CLAuthorizationStatus) {
    // ...
}

func showRequestLocationServiceAlert() {
    // ...
}
  • 위치 권한이 변경되었을 때 호출되는 메서드
  • locationManagerDidChangeAuthorization 메서드가 메인 스레드에서 호출되면 UI 블록이 발생할 수 있음
  • 이 메서드 내에서 checkUserDeviceLocationServiceAuthorization 메서드를 호출하는 것은 긴 시간이 걸려 UI가 응답하지 않는 문제가 발생할 수 있음 → 비동기 처리
  • 비동기적으로 checkUserDeviceLocationServiceAuthorization 메서드를 호출
  • checkUserDeviceLocationServiceAuthorization 메서드에서는 위치 서비스 활성화 여부를 확인하고 권한에 따라 적절한 조치를 취함

 

DispatchQueue.global().async

  • Grand Central Dispatch(GCD)를 사용하여 비동기적으로 작업을 실행하는 방법
  • DispatchQueue.global()은 전역(dispatch global) DispatchQueue 를 반환 함
  • 이는 여러 스레드에서 작업을 동시에 실행할 수 있도록 하는 DispatchQueue
  • async 메서드는 주어진 블록을 비동기적으로 실행할 수 있도록 스케줄링
  • locationManagerDidChangeAuthorization 메서드 내에서 DispatchQueue.global().async 를 사용하면, checkUserDeviceLocationServiceAuthorization 메서드가 백그라운드 스레드에서 비동기적으로 실행되게 됨 → 현재 메인 스레드가 차단되지 않고 다른 작업 수행 가능

 

 

 

5. showRequestLocationServiceAlert 메서드

func showRequestLocationServiceAlert() {
    let requestLocationServiceAlert = UIAlertController(title: "위치 정보 이용", message: "위치 서비스를 사용할 수 없습니다.\\n디바이스의 '설정 > 개인정보 보호'에서 위치 서비스를 켜주세요.", preferredStyle: .alert)
    let goSetting = UIAlertAction(title: "설정으로 이동", style: .destructive) { _ in
        if let appSetting = URL(string: UIApplication.openSettingsURLString) {
            UIApplication.shared.open(appSetting)
        }
    }
    requestLocationServiceAlert.addAction(goSetting)
    // present(requestLocationServiceAlert, animated: true)
}
  • 위치 서비스가 비활성화되어 있는 경우, 사용자가 위치 서비스를 활성화 할 수 있도록 알람 표시
  • 설정으로 이동하는 버튼을 클릭하면 위치 서비스 부분으로 이동