Programming/iOS

[iOS] Location Service - 백그라운드에 있을 때 위치 이벤트 처리하기

devssun 2021. 10. 30. 15:49
728x90
반응형

백그라운드에 있을 때 위치 이벤트 처리하기

원문 링크
handling_location_events_in_the_background

Capability 추가하기

  • 백그라운드에서 위치 이벤트를 처리하려면 프로젝트의 Background Modes를 설정해야 한다.
  • 프로젝트 > 앱 타겟 > Signing & Capabilities 탭으로 들어가서 왼쪽 상단의 +Capability 버튼을 누르고 Background Modes를 추가한다. Location updates 항목을 체크한다.
  • 그리고 CLLocationManager 객체의 allowsBackgroundLocationUpdates property를 반드시 true로 설정한다. (하지않으면 에러 발생)


위치 이벤트 처리하기

  • 앱이 백그라운드에 있는 동안 위치 업데이트가 발생하면 시스템이 앱을 실행하고 application(_:willFinishLaunchingWithOptions:) 또는 application(_:didFinishLaunchingWithOptions:) 메서드에 dictionary를 전달한다. (didFinishLaunchingWithOptions 알아보기)
  • dictionary는 앱이 위치 업데이트로 인해 실행되었음을 알리는 location 키를 포함하고 있을 수도 있다. 만약 그렇지 않은 경우는 아래와 같은 수행이 필요하다.
    • 앱이 실행되고 CLLocationManager 인스턴스가 없는 경우 새 객체를 생성하고 delegate를 구성한다.
    • 위치 모니터링 서비스를 다시 시작한다. 앱의 필요에 따라 startMonitoringVisits(), startMonitoringSignificantLocationChanges(), 또는 startMonitoring(for:) 메서드를 호출한다.
    • 업데이트를 받기 위해 startUpdatingLocation() 를 호출할 필요는 없다.
  • 위치 모니터링 서비스는 Core Location에 모니터링을 중지하도록 지시할 때까지 계속 실행된다. 더이상 위치 업데이트에 따른 앱 실행이 필요하지 않은 경우 모니터링을 중지하는 메서드 stopMonitoringVisits(), stopMonitoringSignificantLocationChanges(), 또는 stopMonitoring(for:) 를 호출한다
  • 새로운 위치 업데이트를 보고하기 위해 앱을 실행하는 서비스, 앱을 다시 실행하는 서비스는 사용자가 앱을 강제 종료한 경우에도 동일하게 한다.

 

Core Location은 사용자가 하나 또는 모든 앱에 대해 백그라운드 앱 새로고침 설정을 비활성화하더라도 백그라운드에서 위치 이벤트를 계속 전달한다. backgroundRefreshStatus 참고

 


위치 모니터링 시작 메서드

위치 모니터링을 하기 위한 메서드는 세개로, 앱의 필요에 따라 적절한 함수를 호출하면 된다.

  1. startMonitoringVisits() : 방문관련 이벤트 전달 시작
    • 이 메서드를 호출하면 방문 관련 이벤트가 앱에 전달되기 시작한다. 한 location manager에 대한 방문 이벤트를 활성화하면 다른 모든 location manager에 대한 방문 이벤트가 활성화된다. 새로운 방문 이벤트가 도착하면 location manager는 해당 이벤트를 locationManager(_:didVisit:) delegate method에 전달한다.
    • 앱은 requestTemporaryPreciseLocationAuthorization(withPurposeKey:) 함수를 호출하지않고도 방문 이벤트를 수집할 수 있다. 이 경우 방문 이벤트는 감소된 정확도를 갖는다.
    • 이 서비스가 활성화되어있는 동안 앱이 종료되면 새로운 방문 이벤트를 전달할 준비가 될때 시스템이 앱을 다시 시작한다.
      다시 시작하면 location manager 객체를 다시 만들고 delegate를 지정하여 방문 이벤트 수신을 시작한다.
      방문 이벤트 전달을 다시 시작하기 위해 이 메서드를 다시 호출할 필요는 없지만 호출해도 문제가 없다.
  2. startMonitoringSignificantLocationChanges() : 중요한 위치 변경을 기반으로 업데이트 생성 시작
    • 이 메서드는 위치 이벤트 전달을 비동기적으로 시작하여 호출한 직후에 반환한다. 위치 이벤트는 delegate method locationManager(_:didUpdateLocations:) 로 전달한다.
    • 전달될 첫 번째 이벤트는 일반적으로 가장 최근에 캐시된 위치 이벤트(있는 경우) 이지만 일부 상황에서는 더 새로운 이벤트일 수 있다.
    • 현재 위치 수정을 반환한 후 수신기는 사용자 위치의 중요한 변경이 감지된 경우에만 업데이트 이벤트를 생성한다. 여러번 호출해도 새 이벤트가 자동으로 생성되지는 않는다.
    • 이 서비스를 시작하고 앱이 종료된 상태에서 새 이벤트가 도착하면 시스템이 자동으로 앱을 백그라운드로 다시 시작한다. 이 경우 AppDelegate의 application(_:willFinishLaunchingWithOptions:)application(_:didFinishLaunchingWithOptions:) 에 위치 이벤트로 인해 앱이 실행되었음을 나타내는 키가 포함된다.
    • 잠재적 오류에 응답하는 메서드도 구현해야 한다.
    • 앱은 기기가 이전 알림에서 500미터 이상 이동하는 즉시 알림을 받을 수 있다. 5분에 한 번 이상 알림을 기대해선 안된다. 장치에 네트워크에서 데이터를 가져올 수 있는 경우 location manager는 적시에 알림을 전달할 가능성이 훨씬 더 높다.
  3. startMonitoring(for:) : 지정된 지역의 모니터링 시작
    • 매개변수 - region : 모니터링할 경계를 정의하는 객체, not nil
    • 모니터링하려는 각 지역에 대해 이 메서드를 한 번만 호출해야한다. 동일한 식별자를 가진 기존 지역이 이미 앱에서 모니터링되고 있는 경우 이전 지역이 새 지역으로 대체된다. 이 방법을 사용하여 추가한 지역은 앱의 모든 location manager 객체에서 공유되고 monitoredRegions 프로퍼티에 저장된다.
    • 지역 이벤트는 아래 메서드에 전달한다. locationManager(_:didEnterRegion:), locationManager(_:didExitRegion:),  locationManager(_:monitoringDidFailFor:withError:)
    • 앱은 한 번에 최대 20개 지역을 등록할 수 있다.

Code

위치 권한 받은 후 위치 이벤트 받아서 지도에 핀 꼽기 예제
https://github.com/devssun/Wonder-Series-iOS/tree/master/NavigationApp

class ViewController: UIViewController {

    @IBOutlet private weak var mapView: MKMapView!
    @IBOutlet private weak var segmentedControl: UISegmentedControl!
    @IBOutlet private weak var addMarkerButton: UIButton!
    @IBOutlet private weak var textView: UITextView!

    private let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.delegate = self
        // 백그라운드 있을 때 위치 업데이트 허용
        locationManager.allowsBackgroundLocationUpdates = true
        // 위치 업데이트 자동 종료 거부
        locationManager.pausesLocationUpdatesAutomatically = false
        // 백그라운드 위치 인디케이터 노출
        locationManager.showsBackgroundLocationIndicator = true
        // 위치 정확도 최고 설정
        locationManager.desiredAccuracy = kCLLocationAccuracyBest

        // 위치 권한 요청
        locationManager.requestAlwaysAuthorization()

        if CLLocationManager.locationServicesEnabled() {
            // 위치 서비스 사용이 가능하다면 locationManager에 현재 위치를 요청한다
            locationManager.requestLocation()
        }

        mapView.showsUserLocation = true
    }

    fileprivate func setAnnotation(coordinate: CLLocationCoordinate2D) -> MKPointAnnotation {
        let annotation = MKPointAnnotation()
        annotation.coordinate = coordinate
        return annotation
    }

    fileprivate func getCenterCoordinate() -> CLLocationCoordinate2D {
        return mapView.centerCoordinate
    }

    @IBAction private func touchedAddMarkerButton(_ sender: UIButton) {
        let annotation = setAnnotation(coordinate: getCenterCoordinate())
        mapView.addAnnotation(annotation)
    }

    @IBAction private func touchedSegmentedControl(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0:
            print("start monitoring")
            locationManager.startMonitoringSignificantLocationChanges()
        case 1:
            print("stop monitoring")
            locationManager.stopMonitoringSignificantLocationChanges()
        case 2:
            let annotations = mapView.annotations
            mapView.removeAnnotations(annotations)
        default:
            break
        }
    }
}

extension ViewController: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let lastLocation = locations.first {
            // 위치 업데이트되면 하는 일
            mapView.centerToLocation(lastLocation)
            mapView.addAnnotation(setAnnotation(coordinate: lastLocation.coordinate))
            textView.text.append("위치 \(lastLocation.coordinate)\n")
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        textView.text = "에러 발생 \(error.localizedDescription)"
    }
}

private extension MKMapView {
    func centerToLocation(_ location: CLLocation, regionRadius: CLLocationDistance = 1000) {
        let coordinateRegion = MKCoordinateRegion(
            center: location.coordinate,
            latitudinalMeters: regionRadius,
            longitudinalMeters: regionRadius)
        setRegion(coordinateRegion, animated: true)
    }
}

 


노션에 작성 후 블로그에 올리고 있는데 가독성이 너무 떨어진다..

글쓰기 화면이랑 실제 보는 화면이랑 다르게 나와서 좀 문제네요 스킨을 바꿔야 하나?

반응형