본문 바로가기
Swift

CoreLocation) 문서 공부2 (CLLocationManager)

by 김 Duke 2024. 2. 6.

목차

    오늘 알아볼 CLLocationManager의 경우에는 CoreLocation의 핵심이라고도 볼 수 있다.

    CLLocationManager와 관련하여 많은 문서들이 있는데,

    그 중에서도 CoreLocation의 핵심 기능을 알아보고, 위치정보를 받아오는 다양한 방법들을 공부해보려고 한다.


     

     

    CLLocationManager가 뭔데?

     

    https://developer.apple.com/documentation/corelocation/cllocationmanager

     

    해석하자면 CLLocationManager 객체는 앱의 위치 관련 동작을 관리하는 중심 장소이다. location manager객체를 사용하여 위치 서비스를 구성, 시작 및 중지한다.

     

    정리하면 CoreLocation의 위치 관련 API에 접근하기 위한 관리자인 셈이다. 

    관리자는 위치정보 측정 및 정확도 조절, 위치서비스 권한요청, 위치 측정에 필요한 기기의 사용 가능여부를 관리한다.

    관리자를 사용한다는 것은 CoreLocation을 사용한다는 것과 같은 맥락으로 보면 된다.

    https://developer.apple.com/documentation/corelocation/cllocationmanager

     

    CLLocationManager | Apple Developer Documentation

    The object that you use to start and stop the delivery of location-related events to your app.

    developer.apple.com

    https://developer.apple.com/documentation/corelocation/configuring_your_app_to_use_location_services

     

    Configuring your app to use location services | Apple Developer Documentation

    Verify that location services are available and configure your app before you start collecting location data.

    developer.apple.com

     

    CLLocationManager 기본 구성

    임의의 LocationManager 클래스를 생성 후, CLLocationManager 객체를 생성하고 델리게이트를 선언해주었다.

    class LocationManager: NSObject {
        let locationManager = CLLocationManager()
    
        override init() {
            super.init()
            locationManager.delegate = self
        }
    }
    
    extension LocationManager: CLLocationManagerDelegate {
    }

     

     

     

    위치정보에 다가가기 위해 위치 서비스 이용 권한을 얻어야한다. 이에 대해선 아래글에 정리해두었다.

    2024.02.01 - [Swift] - CoreLocation) 위치정보 이용에 필요한 승인 권한 요청(사용중, 항상)

     

    CoreLocation) 위치정보 이용에 필요한 승인 권한 요청(사용중, 항상)

    CoreLocation을 사용하며 위치정보를 이용하기 위해 권한이 필요하다. 애플의 문서에 따르면 위치 데이터는 민감하고 앱 사용자의 개인정보 보호에 영향을 미치기 때문에 섬세한 요청 방식을 요구

    yahoth.tistory.com


     

     

    requestWhenInUseAuthorization 메소드를 통해 이용 권한을 요청할 수 있고 승인했을 경우 사용자 기기의 위치 정보에 접근이 가능하다.

        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.requestWhenInUseAuthorization()
        }

     

     

     

    CoreLocation의 핵심 기능 3가지

    CoreLocation의 핵심 기능은 위치 기반 서비스를 이용하는 것이다.

    1. 장치의 위치 추적: GPS, Wi-Fi, 셀룰러, iBeacons 등을 통해 장치의 현재 위치 정보를 얻을 수 있다. 이 정보는 맵 표시나 위치 기반 알림 등에 사용될 수 있다.
    2. 지역 모니터링: 특정 지역에 대한 진입 및 이탈을 감지할 수 있다. 예를 들어, 사용자가 특정 상점에 들어가거나 나올 때 알림을 보내는 등의 기능을 구현할 수 있다.
    3. 방향 및 속도 추적: CoreLocation은 장치의 이동 방향과 속도를 추적할 수 있게 해주며, 이를 활용해 네비게이션 앱을 개발하는데 유용하다.

     

    기능을 가능하게 해주는 메소드들

     

    표준 위치 서비스: Standard Location Service

    CoreLocation의 메인 서비스이다. 지정한 거리<distanceFilter:관련하여 정확성 파트에서 다루겠다>를 초과하여 움직였을 경우 위치정보를 수집하여 알려준다.

    위치정보는 CLLocation 객체로 앞서 작성한 문서1에서 다뤘다.

    2024.02.01 - [Swift] - CoreLocation) 문서공부1 (현재 위치 받기, CLLocation 클래스 파헤치기)

     

    CoreLocation) 문서공부1 (현재 위치 받기, CLLocation 클래스 파헤치기)

    CoreLocation의 위치정보를 기반으로 하는 앱을 만들고 있다. 문서에서 필요한 정보만 보면서 앱을 만들었는데, 완성이 다가올 수록 부족한 부분이 보였다. 그 동안 CoreLocation이라는 프레임워크를

    yahoth.tistory.com

     

    startUpdatingLocation 메소드를 통해 위치 정보를 업데이트하기 시작하고, CLLocationManager의 델리게이트 메소드인 didUpdateLocations에서 업데이트한 위치정보를 확인할 수 있다.

    func startUpdatingLocation()

     

    optional func locationManager(
        _ manager: CLLocationManager,
        didUpdateLocations locations: [CLLocation]
    )

     

    class LocationManager: NSObject {
        let locationManager = CLLocationManager()
        var previousLocation: CLLocation?
    
        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.requestWhenInUseAuthorization()
        }
    
        func start() {
            print("*** Start Updating Location ***\n")
            locationManager.startUpdatingLocation()
        }
    
        func stop() {
            print("*** Stop Updating Location ***")
            locationManager.stopUpdatingLocation()
    
        }
    }
    
    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            guard let location = locations.last else { return }
            let coordinate = location.coordinate
            let speed = location.speed
            let horizontalAccuracy = location.horizontalAccuracy
            let speedAccuracy = location.speedAccuracy
            print("--------------------------------------------------------------------------------------\n")
            print("Coordinate: \(coordinate), accuracy: \(horizontalAccuracy)\n")
            print("Speed: \(Int(speed))M, accuracy: \(speedAccuracy)\n")
            if let previousLocation {
                let time = location.timestamp.timeIntervalSince(previousLocation.timestamp)
    
                print("period: \(Int(time))s")
            }
            previousLocation = location
        }
    
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print(error)
        }
    }
     
    시뮬레이터 테스트 결과
     
    환경: 시뮬레이터의 자동차모드
     
    CLLocation 객체의 일부 데이터를 출력했다.
    Coordinate: 위치좌표(coordinate), accuracy: 좌표 정확도(horizontalAccuracy)
    Speed: 속도(speed), accuracy: 속도 정확도(speedAccuracy)

    업데이트를 시작하고 약 4초 후 업데이트를 중지한 결과이다.

    첫번째 출력 후 두번째 출력부터는 마지막줄에 period가 보인다. 이는 1️⃣번 출력물 이후 2️⃣출력물이 생성되기까지의 시간인데 초(seconds)라고 생각하여 Int타입으로 프린트 했더니 0초가 섞여있다.

     

    Double타입 그대로 출력한 결과 대략 1초에 한번씩 출력되는 것을 볼 수 있다.

    매 초 생성되는 CLLocation 객체를 이용하여 속도 및 방향 등을 확인할 수 있고 이를 이용하여 내비게이션 앱을 만들 수 있다.

     

     

    ✅ 중요한 위치 변화 모니터링:  Significant Change Location Service

    말그대로 시스템에서 중요한 위치 변화라고 느껴질 때 위치정보를 알려준다.

    위에서 다뤘던 매 초 마다 위치 정보를 알려주는 standard location service와 는 다르게 중요한 위치변화를 감지하여 일정 기준에 맞춰서 알림을 보낸다. 문서에 의하면 중요한 위치 변화의 기준500m 혹은 그 이상이 될 수 있다고 한다. 

    해당 옵션은 정확한 정보보단 대략적인 위치 정보를 파악할 때 유용하게 사용할 수 있다.

     

    startMonitoringSignificantLocationChanges 메소드를 통해 위치 정보를 업데이트하기 시작하고, 위에서와 마찬가지로  CLLocationManager의 델리게이트 메소드인 didUpdateLocations에서 업데이트한 위치정보를 확인할 수 있다.

    func startMonitoringSignificantLocationChanges()
    optional func locationManager(
        _ manager: CLLocationManager,
        didUpdateLocations locations: [CLLocation]
    )
    class LocationManager: NSObject {
        let locationManager = CLLocationManager()
        var previousLocation: CLLocation?
    
        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.requestWhenInUseAuthorization()
        }
    
        func startMonitoringSignificant() {
            print("*** Start Monitoring the Significant Location Changes ***")
            locationManager.startMonitoringSignificantLocationChanges()
        }
    }
    
    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            guard let location = locations.last else { return }
            if let previousLocation {
                let time = location.timestamp.timeIntervalSince(previousLocation.timestamp)
                let distance = location.distance(from: previousLocation)
                print("distance: \(Int(distance))M, period: \(Int(time))s")
            }
            previousLocation = location
        }
    
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print(error)
        }
    }

     

     

    시뮬레이터 테스트 결과

    시뮬레이터의 자전거 모드로 테스트 했을 때 500m마다 알림을 보내줬다.

    하지만 자동차 모드의 경우 속력이 느린 초반엔 500m 기준으로 알림을 보내다, 

    속도가 붙자 30초에 한 번 알림을 보냈다.

     

    이동 거리를 기준으로 알림을 보내지 않은 것에 의아했지만, 해당 메소드의 동작 방식은 유사하다.

    실제 환경에서의 테스트 결과가 아니기 때문에 약간 다를 수 있음을 참고 하길 바란다.

    자전거 

    자동차

    https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati

     

    startMonitoringSignificantLocationChanges() | Apple Developer Documentation

    Starts the generation of updates based on significant location changes.

    developer.apple.com

     

     

    ✅ 방문 모니터링 서비스: Visits Location Service

    방문 모니터링 서비스는 처음 사용해봐서 좀 생소하기도 했고, 문서가 애매모호해서 구글링을 통해 정보를 얻었다.

    사용자가 어떤 위치에 방문했다고 시스템 상에서 판단을 하면 delegate메소드인 "didVisit"이 호출된다.

    didVisit메소드가 제공하는 CLVisit객체는 좌표, 좌표정확도, 방문시작시간, 방문종료시간의 정보를 가진다.

    가장 처음 호출되는 CLVisit 객체는 방문시작시간(arrivalDate)만을 가진다. 이는 방문 시작 시간을 의미한다.

    이후 방문이 종료됐다고 시스템상에서 판단하면 방문종료시간(departureDate)을 포함한 완전한 CLVisit객체를 호출한다.

    func startMonitoringVisits()
    optional func locationManager(
        _ manager: CLLocationManager,
        didVisit visit: CLVisit
    )

     

    하지만 해당 구현의 경우 실 기기로 테스트해보려고 했으나, didVisit 메소드의 호출이 전혀 되지 않았다.

    이후 실외에서 테스트하여 결과를 올려보겠다.

    class LocationManager: NSObject {
        let locationManager = CLLocationManager()
    
        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.requestWhenInUseAuthorization()
            locationManager.allowsBackgroundLocationUpdates = true
            locationManager.showsBackgroundLocationIndicator = true
        }
    
        func start() {
            locationManager.startMonitoringVisits()
        }
    
        func stop() {
            locationManager.stopMonitoringVisits()
        }
    }
    
    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
            if visit.departureDate == Date.distantPast {
                print("방문 시작: \(visit.arrivalDate)")
            } else {
                print("방문 종료: \(visit.departureDate)")
            }
        }
    
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            logs.append(Log(text: error.localizedDescription))
        }
    }

    https://developer.apple.com/documentation/corelocation/cllocationmanager/1618692-startmonitoringvisits

     

    startMonitoringVisits() | Apple Developer Documentation

    Starts the delivery of visit-related events.

    developer.apple.com

    https://developer.apple.com/documentation/corelocation/clvisit

     

    CLVisit | Apple Developer Documentation

    Information about the user's location during a specific period of time.

    developer.apple.com

     

    ✅ 방향 정보 서비스: Heading Service

    기기가 바라보고 있는 방향을 나타낸다. "startUpdatingHeading" 메소드를 통해서 측정을 시작하고 "didUpdateHeading" 델리게이트 메소드를 통해서 방향정보를 받아볼 수 있다. 

    여기서 방향정보를 나타내는 객체인 CLHeading은 대표적으로 trueHeadingmagneticHeading 프로퍼티를 가진다.

    trueHeading: CLLocationDirection = 지리적 북쪽을 기준으로 하는 방향
    magneticHeading: CLLocationDirection = 지구의 자기장, 즉 자기 북쪽을 기준으로 하는 방향

    이 둘의 차이를 이해하기 어려워서 더 알아본 결과,

    trueHeading은 지구의 자전축이 지나는 북극점을 가리키는 방향, 즉 일반적으로 말하는 방위값, 지도에서 사용하는 방향이고

    magneticHeading은 지구 내부의 자기장이 지나는 점을 가리키는 방향이라고 한다.

    이 둘의 값이 같은 북쪽을 가리키더라도 실제 값과 약간 다를 수 있고, 일반적으로는 trueHeading을 사용한다고 한다.

     

    값이 0일경우 북쪽, 180일 경우 남쪽을 가리킨다.

     

    CLHeading 객체를 사용하여 내비게이션 앱에서 사용자가 향하는 방향으로 지도를 회전하는데 사용할 수 있고, 나침반앱에서 방위를 나타낼 수도 있다.

     

     

    func startUpdatingHeading()
    optional func locationManager(
        _ manager: CLLocationManager,
        didUpdateHeading newHeading: CLHeading
    )
    class LocationManager: NSObject {
        let locationManager = CLLocationManager()
    
        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.requestWhenInUseAuthorization()
        }
    
        func start() {
            locationManager.startUpdatingHeading()
        }
    
        func stop() {
            locationManager.stopUpdatingHeading()
        }
    }
    
    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
            var heading: String = ""
    
            if newHeading.trueHeading > 45 && newHeading.trueHeading <= 135 {
                heading = "동쪽"
            } else if newHeading.trueHeading > 135 && newHeading.trueHeading <= 225 {
                heading = "남쪽"
            } else if newHeading.trueHeading > 225 && newHeading.trueHeading <= 315 {
                heading = "서쪽"
            } else {
                heading = "북쪽"
            }
    		print("heading")
        }
    
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        }
    }

     

     

    테스트 결과

    환경: 실 기기 테스트

    시뮬레이터에선 방향을 테스트할 수 없기 때문에 실기기에서 테스트했다.

    간단하게 방위를 4가지로 나누어서 기기가 바라보는 방향을 출력하는 테스트를 실시했다.

    테스트 결과, 기기의 방향이 전환될 때 예민하게 반응하는 것을 볼 수 있다.

     

    MapKit의 MKMapView를 사용하면 CLLocationManager의 heading 관련 기능을 구현하지 않아도 시각적으로 표현이 가능하다.

    하지만 mapView 하단의 heading데이터는 기능을 구현해야만 받을 수 있다.

    두 기능을 함께 사용한다면 보다 효율적으로 사용할 수 있을 것이다.

    https://developer.apple.com/documentation/corelocation/clheading

     

    CLHeading | Apple Developer Documentation

    The azimuth (orientation) of the user’s device, relative to true or magnetic north.

    developer.apple.com

     

     

    ✅ 지역  모니터링 (지오펜싱): Region Monitoring Service (Geofencing)

     
    특정 지역이나 지점을 모니터링하는 서비스이다. 
    지역을 설정한 뒤 그 위치에 대한 출입을 감지한다.
    예를 들어 특정 상점이나 관광 명소에 접근하면 알림을 보내거나, 특정 지역에 들어갔을 때 특별한 동작을 수행하는 등의 기능을 구현할 수 있다.
    "startMonitoring"메소드를 통해 모니터링(감지)할 지역을 설정하고 "didEnterRegion","didExitRegion" 델리게이트 메소드를 통해 지역을 모니터링하며 생기는 이벤트(출입) 정보를 받아볼 수 있다.
     
    여기서 CLRegion 객체는 지역을 의미하고 예시코드에서는 CLRegion을 상속한 원형 지역인 CLCircularRegion객체를 사용했다.
    func startMonitoring(for region: CLRegion)
    optional func locationManager(
        _ manager: CLLocationManager,
        didEnterRegion region: CLRegion
    )
    optional func locationManager(
        _ manager: CLLocationManager,
        didExitRegion region: CLRegion
    )​
    struct Log: Hashable {
        let id: UUID = UUID()
        let text: String
    }
    
    class LocationManager: NSObject {
        let locationManager = CLLocationManager()
        @Published var logs: [Log] = []
        @Published var region: CLCircularRegion?
        var subscriptions = Set<AnyCancellable>()
    
        override init() {
            super.init()
            locationManager.delegate = self
            locationManager.requestWhenInUseAuthorization()
        }
    
        func start() {
            let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 37.33497685282217, longitude: -122.04664142846063), radius: 500, identifier: "Homestead High School")
            self.region = region
            monitorRegionAtLocation(region: region)
        }
    
        func monitorRegionAtLocation(region: CLCircularRegion) {
            if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
                region.notifyOnEntry = true
                region.notifyOnExit = true
                locationManager.startMonitoring(for: region)
                logs.append(Log(text: "*** Start Monitoring ***"))
            }
        }
    
        func stop() {
            logs.append(Log(text: "*** Stop Monitoring ***"))
            locationManager.stopMonitoring(for: region ?? CLCircularRegion())
            region = nil
        }
    }
    
    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
            if let region = region as? CLCircularRegion {
                let identifier = region.identifier
                logs.append(Log(text: "Enter region: \(identifier)"))
            }
        }
    
        func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
            if let region = region as? CLCircularRegion {
                let identifier = region.identifier
                logs.append(Log(text: "Exit region: \(identifier)"))
            }
        }
    
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            logs.append(Log(text: error.localizedDescription))
        }
    }​
     
     
    테스트 결과
    환경: 시뮬레이터 Freeway Drive
    Freeway Drive로 설정하면 큰 사거리에서 출발한다.
    이곳과 멀지 않은 근처 고등학교를 모니터링 지역으로 설정하고 시뮬레이터를 녹화했다.
    관련하여 전체코드는 여기서 확인할 수 있다. 
     
     

    GitHub - yahoth/CoreLocationPractice: CoreLocation 문서 뜯어보기용 저장소

    CoreLocation 문서 뜯어보기용 저장소. Contribute to yahoth/CoreLocationPractice development by creating an account on GitHub.

    github.com

     

     

     

    https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1423630-locationmanager

     

    locationManager(_:didExitRegion:) | Apple Developer Documentation

    Tells the delegate that the user left the specified region.

    developer.apple.com

    https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions

     

    Monitoring the user's proximity to geographic regions | Apple Developer Documentation

    Use region monitoring to determine when the user enters or leaves a geographic region.

    developer.apple.com

    https://developer.apple.com/documentation/corelocation/cllocationmanager/1423656-startmonitoring

     

    startMonitoring(for:) | Apple Developer Documentation

    Starts monitoring the specified region.

    developer.apple.com

     

    결론

    CoreLocation에서 제공하는 위치 관련 API들을 대부분 만나보았다.

    안다룬 부분이 몇몇 있으나 위의 API들의 기능을 이해한다면 어렵지 않을 것 같다.

    내가 사용해봤던 표준 위치 서비스와 방향 정보 외에는 이번 문서 공부를 통해 처음 접했다.

    느낌은 대충 알고 있었으나 직접 사용해보니 강력한 API라는것을 알 수 있었고 CoreLocation이라는 프레임워크와 한층 가까워진 기분이다.

     

    그리고 기능 마다 레퍼런스를 정리해뒀는데 그부분을 제대로 알기 위해선 꼭 공식 문서를 확인해보길 바란다.


    TOP

    Designed by 티스토리