Xcodeで端末の位置情報をシミュレーションする方法

# 概要
unerry モバイルエンジニアの濱田です。
[unerry Advent Calendar 2024](https://qiita.com/advent-calendar/2024/unerry) 12/19 の記事です。

本記事では、iPhoneの位置情報を任意に設定し、位置情報を使ったアプリのデバッグを簡単にする方法を説明します。

# はじめに
位置情報を活用したiOSアプリを開発していると、任意の地点に移動した時のアプリの動きを確認したい、というケースがあります。
iPhoneを持って、その場に移動できれば、問題ありませんが、開発中では、その場に移動して確認をすることが難しい場合があります。
例えば、その場所への移動をした前後のログを、確認する必要がある、といった場合です。
このような時、Xcodeで移動をシミュレーションすることで、任意の地点に移動した時のアプリの動きを、その場で確認することができます。
本記事では、前述したようなXcodeを使った移動のシミュレーション方法を解説します。

# 背景
unerryのサービスであるBeacon Bank は、ビーコンやジオフェンスを利用したジオプッシュ通知を提供しています。
Beacon Bank SDKの開発では、ビーコンやジオフェンスの監視や検出、プッシュ通知に関する実装を行っています。
位置情報に基づいた何らかの処理の検証を行う際、以下のように、ピンポイントの位置での挙動を確認したい、という場合があります。

* 設定したジオフェンスの境界付近に、ユーザーがいる
* 設定したジオフェンスを、ユーザーが短い間隔で出入りしている

上記のケースを検証する場合、以下の手順が考えられます。

* 端末で取得できている位置情報を見ながら、移動する

この方法は、デバッグ実行など、アプリの状態を確認しながら行うには、手順が煩雑となります。
また、検証した内容を共有する上でも、再現しにくくなる、という課題もあります。
そのため、MacをiPhoneと接続した状態で、簡易にデバッグできる方法が必要になります。

Xcodeには、プロジェクトに追加されているGPXファイル [^gpx] を読み込むことで、ユーザーの移動をシミュレーションするという機能があります。
この機能を利用することで、デバッグ実行で、任意の位置への移動を再現することができます。

# 手順
以下の手順で、Xcodeを使ったデバッグで、ユーザーの移動のシミュレーションが行えます。

1. GPXファイルを用意する
    1. Xcodeでプロジェクトを開く
    1. File > New > File from Template を選択する
    1. Resource 欄にある GPX File を選択する
    1. Next ボタンをクリックする
    1. ファイル名を入力する
    1. Create ボタンをクリックする
    1. プロジェクトに、GPXファイルが追加される
    1. Xcode 上で、作成したGPXファイルを編集する
1. アプリをデバッグ実行する
1. 移動のシミュレーションを実行する

## GPXファイルの用意と編集
移動のシミュレーションを行うためには、シミュレーションする地点の座標を記述したファイルが必要になります。
移動する地点の座標を記述したファイルが、GPXファイルです。
GPXファイルは、GPSデータ（地点、ルート、進路）を記述したXMLファイルです。
Xcodeのデバッグで使用するGPXファイルでは、地点の情報を記述します。
GPXファイルに複数の地点を記述することで、ユーザーが地点Aから地点Bへ移動する、などの移動を記述できます。

GPXファイルで、移動を記述する際は、以下のような形で、記述を行います。

* `wpt` タグで、地点情報を記述する
* `wpt` の要素に、以下のものを追加する
    * `name` タグで、地点の名前を記述する
    * `time` タグで、その地点に到着した時間を記述する

ルート (`rte` タグ) や、進路 (`trk` タグ)などは、Xcodeでは扱えないため、外部サービスで作成したGPXファイルを使用する場合は、適当な変換を行った上で使用してください。

以下に、東京駅から、上野駅に行く経路を地点情報として記述した例を示します。
時間は、山手線に乗車している場合を想定し、記述しています。

```:xml
<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
    <wpt lat="35.681111" lon="139.766667">
        <name>東京駅</name>
        <time>2024-12-01T15:04:00Z</time>
    </wpt>
    <wpt lat="35.691667" lon="139.770833">
        <name>神田駅</name>
        <time>2024-12-01T15:06:00Z</time>
    </wpt>
    <wpt lat="35.698333" lon="139.773056">
        <name>秋葉原駅</name>
        <time>2024-12-01T15:08:00Z</time>
    </wpt>
    <wpt lat="35.707417" lon="139.774639">
        <name>御徒町駅</name>
        <time>2024-12-01T15:09:00Z</time>
    </wpt>
    <wpt lat="35.713889" lon="139.777222">
        <name>上野駅</name>
        <time>2024-12-01T15:11:00Z</time>
    </wpt>
</gpx>
```

GPXファイルに、地点情報を記述する際は、以下のことに注意します。

* GPXファイルに書かれている順で移動していく
    * 例では、上から順に「東京駅」→「神田駅」→「秋葉原駅」→「御徒町駅」→「上野駅」の順に移動をしていく
* 地点間の移動は、以下のようになる
    * 2点間を直線距離で移動する
    * 2点間の移動速度は、地点に設定されている時間差から、等速で移動する
* ある地点への滞在を表現することはできない
    * 同じ地点を連続して記述し、時間だけを変更しても、無視される
    * 擬似的に滞在を表現する場合、少しだけ、緯度を変更する、など、影響が少ない範囲で移動を行う
        * 例
            ```
            <wpt lat="35.713889" lon="139.777222">
                <name>上野駅 - 到着</name>
                <time>2024-12-01T15:11:00Z</time>
            </wpt>
            <wpt lat="35.713890" lon="139.777222">
                <name>上野駅 - 出発</name>
                <time>2024-12-01T15:21:00Z</time>
            </wpt>
            ```

## 移動のシミュレーション
前節で作成したGPXファイルを使用し、デバッグ中のアプリで移動のシミュレーションを行います。
通常のアプリのデバッグ実行と同じく、移動のシミュレーションを行いたいアプリをXcodeから、デバッグ実行で起動します。
デバッガーが起動すると、デバッグエリアのツールバーの表示が切り替わります。
ツールバーに表示されているボタンから、様々なデバッグの機能が利用できるようになります。
デバッグエリアのツールバーにある「ロケーションボタン」をクリックすると、端末の位置情報のシミュレーションが行えます。

![step5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3959454/103baed6-0988-6ab8-7b18-930b7cf2ec19.png)
赤枠で示す「ロケーションボタン」をクリックし、ロケーションのメニューを表示します。

![step6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3959454/1f07bf0c-135c-676a-7cf8-2ea07359f8bf.png)
「ロケーション」ボタンをクリックすると、図のようなメニューが表示されます。
メニューには、以下の項目が表示されます。

* シミュレーションの中止
* プロジェクトに配置されているGPXファイルの一覧
    * メニューでは、拡張子が非表示となっています
* プリセットとして定義されている都市の一覧
* GPXファイルの追加

メニューから、前節の例で示したGPXファイルを選択すると、移動のシミュレーションが開始します。
移動や位置情報のシミュレーションを行っている場合、ツールバーのロケーションボタンが、青く塗りつぶされます。

前節の例で示したGPXファイルのシミュレーションを行うと、以下の図のように、移動のシミュレーションが行われます。

![location_simulation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3959454/233b0063-fc89-811c-5a25-9c2ca7ea06bc.gif)
移動のシミュレーションを行う前は、オフィスのある神谷町が現在地として表示されています。
移動のシミュレーションを開始すると、最初の地点である東京駅に移動します。
その後、GPXファイルの記述に従って、東京駅から神田駅に向かって、移動を始めます。

# アプリで、シミュレーションされた位置情報かを判定する
本記事で解説した移動のシミュレーションは、端末の位置情報を変更して、アプリへ通知されます。
そのため、現在デバッグしていないアプリにも、シミュレーションしている位置情報が現在位置として渡されます。
Core Locationでは、シミュレーションされた位置情報かどうかを判定する方法を提供しています。
アプリでは、この情報を利用し、意図しないタイミングで、シミュレーションされた位置情報が渡されていないかを判定することができます。

Core Locationでは、位置情報は [`CLLocation`](https://developer.apple.com/documentation/corelocation/cllocation) のデータとして渡されます。
`CLLocation` には、 [`sourceInformation`](https://developer.apple.com/documentation/corelocation/cllocation/sourceinformation) というプロパティが定義されています。
このプロパティは、iOS 15.0で追加されたものです。
このプロパティは、 [`CLLocationSourceInformation`](https://developer.apple.com/documentation/corelocation/cllocationsourceinformation) 型のデータとなっており、 [`isSimulatedBySoftware`](https://developer.apple.com/documentation/corelocation/cllocationsourceinformation/issimulatedbysoftware) の値をチェックすることで、位置情報がシミュレーションされたものかを判定できます。
GPXファイルをシミュレーションしている場合、 `isSimulatedBySoftware` の値が、 `true` となります。
以下に、位置情報がシミュレーションされたものかどうかを判定するコードを示します。

```swift
func locationManager(_ manager: CLLocationManager,
                     didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.first else {
        return
    }
    if location.sourceInformation?.isSimulatedBySoftware ?? false {
        NSLog("Location is simulated by GPX.")

        // シミュレーションされた位置情報なので無視する
        return
    } else {
        NSLog("Location is not simulated by GPX.")
    }
}
```

# まとめ
本記事では、GPXファイルを利用したiPhoneでの移動のシミュレーション方法を説明しました。
Xcodeで使用するGPXファイルの記述方法とその利用方法の説明を行いました。
また、アプリ側で、Core Locationから渡された位置情報がシミュレーションされたものかどうかを判定する方法についても述べました。

位置情報を活用したアプリを開発する際には、このような方法を使うと、より効率的に開発を進めていくことができます。

# リファレンス
[^gpx]: "GPX: the GPS Exchange Format." https://www.topografix.com/gpx.asp