Swift5でアプリが終了している状態で位置情報を取得する方法

今回はアプリが終了(キル)している状態で位置情報を取得する方法をご紹介します。

通常iOSアプリが終了している状態では、一切のプログラムを走らせることができません。

しかし、位置情報に関してだけは、アプリが終了している状態でもiOSが

アプリを自動で起動 → 位置情報をアプリで取得 → アプリを自動で終了

というような処理をしてくれる方法があります。

今回はこちらを利用します。

アプリが終了している状態で位置情報を取得する方法

実装手順

  1. 位置情報を取得できるようにする
  2. startMonitoringSignificantLocationChanges()を使用してアプリ終了時でも位置情報を取得できるようにする
  3. AppDelegate内で再度、startMonitoringSignificantLocationChanges()を使用して再帰するようにする

サンプルコード

import UIKit

import CoreLocation

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    var locationManager : CLLocationManager!

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // ログのチェック
        self.checkLogs()
        
        locationManager = CLLocationManager.init()
        locationManager.allowsBackgroundLocationUpdates = true; // バックグランドモードで使用する場合YESにする必要がある
        locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 位置情報取得の精度
        locationManager.distanceFilter = 1; // 位置情報取得する間隔、1m単位とする
        locationManager.delegate = self
        CLLocationManager.authorizationStatus()
        
        // 位置情報起因で起動した場合のみ処理する
        if launchOptions?[.location] != nil {
            self.addLog("Launched with UIApplicationLaunchOptionsKey.location")
            locationManager.startMonitoringSignificantLocationChanges()
        }
        
        return true
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // バックグラウンドに入る前
        locationManager.startMonitoringSignificantLocationChanges()
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // フォアグランド
        locationManager.startMonitoringSignificantLocationChanges()
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // アプリが終了(キル)される前
        locationManager.startMonitoringSignificantLocationChanges()
    }
    
    // アプリ終了時でも位置情報ログを取れるようにUserDefaultsに保存する
    func addLog(_ log: String) -> Void {
        if UserDefaults.standard.object(forKey: "logs") == nil {
            UserDefaults.standard.set([], forKey: "logs")
        }
        var logs : [String] = UserDefaults.standard.object(forKey: "logs") as! [String]
        logs.append(log)
        UserDefaults.standard.set(logs, forKey: "logs")
    }
    
    // 保存したログをチェックする
    func checkLogs() -> Void {
        if UserDefaults.standard.object(forKey: "logs") != nil {
            let logs : [String] = UserDefaults.standard.object(forKey: "logs") as! [String]
            print("logs:\(logs)")
        }
    }
    
    // 保存したログを破棄する
    func removeLogs() -> Void {
        UserDefaults.standard.set([], forKey: "logs")
    }
}

extension AppDelegate: CLLocationManagerDelegate {

     func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
         // 位置情報を取得してログとして追加
         let location : CLLocation = locations.last!;
         self.addLog(location.description)
     }
    
     func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
         print("error:\(error)")
     }
    
     func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
         if (status == .restricted) {
             print("機能制限している");
         }
         else if (status == .denied) {
             print("許可していない");
         }
         else if (status == .authorizedWhenInUse) {
             print("このアプリ使用中のみ許可している");
         }
         else if (status == .authorizedAlways) {
             print("常に許可している");
             locationManager.startUpdatingLocation();
         }
     }
}

実装における注意点

1. バックグラウンドで位置情報を取得できるようにする

Capability > BackgoundModes > Location Updates

の設定をしましょう。

locationManager.allowsBackgroundLocationUpdates = true; // バックグランドモードで使用する場合YESにする必要がある

こちらのプログラムも忘れずに設定しましょう。

2. AppDelegate内で処理させる必要がある

iOSが自動で「アプリを自動で起動 → 位置情報をアプリで取得 → アプリを自動で終了」してくれますが、処理が走る箇所は

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
	// ここだけ
}

AppDelegateのapplication(_ application: didFinishLaunchingWithOptions launchOptions: )だけとなっています。

注意しましょう。

3. 再帰処理を記述する

locationManager = CLLocationManager.init()
locationManager.allowsBackgroundLocationUpdates = true; // バックグランドモードで使用する場合YESにする必要がある
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 位置情報取得の精度
locationManager.distanceFilter = 1; // 位置情報取得する間隔、1m単位とする
locationManager.delegate = self
CLLocationManager.authorizationStatus()

// 位置情報起因で起動した場合のみ処理する
if launchOptions?[.location] != nil {
    self.addLog("Launched with UIApplicationLaunchOptionsKey.location")
    locationManager.startMonitoringSignificantLocationChanges()
}

startMonitoringSignificantLocationChanges()を使用してアプリが終了している状態で1度でも位置情報を取得するとstartMonitoringSignificantLocationChanges()の効果は終了します。

アプリが終了している状態で、定期的に位置情報を取得したい場合はstartMonitoringSignificantLocationChanges()をAppDelegateのapplication(_ application: didFinishLaunchingWithOptions launchOptions: )で再び呼び出す必要があります。

4. アプリ終了状態では位置情報の取得間隔が広い

アプリ終了状態で位置情報は取得できるのですが、試したところ2~3km歩かないと位置情報は取得できないようでした。

どうやら、大幅に位置が変わった時のみ、位置情報が取得できるようです。

Appleとしてはアプリ終了時に精度が高い位置情報を提供してしまうと、位置情報を利用した悪意ある攻撃ができてしまうため、あえてこうしているのだと思います。

まとめ

いくつか注意しなければいけない点がありますが実装は簡単ですね。

ちなみにですがサンプルコードでは、ちゃんとアプリ終了状態で位置情報がとれいるか確認できるようにログが取れるようになっています。

気になる方は試してみてください。