[iOS] CoreMotionでCMErrorTrueNorthNotAvailableが発生するちょっと意外な原因


iOSのCoreMotionを使って、磁北ではなく真北を基準として端末姿勢を取得しようとする。だいたいこんな感じのコードになる。

let motionManager = CMMotionManager()
if motionManager.isDeviceMotionAvailable {
    motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: .main) { (motion, error) in
        // Do something
    }
}

すると、このハンドラにCMErrorTrueNorthNotAvailableというエラーが渡ってくる場合がある。そのまんま「真北が使えない」ということだが、原因がけっこうわかりにくい。結論をいうと、考慮すべき原因として以下のようなものがある:

  • 何らかの理由で位置情報が取れていない (これは公式ドキュメントにも書かれている)
  • (たとえ現在地が取得できている状態であっても) iOSの設定でコンパスの調整 (Compass Calibration) が無効になっている

この2つ目がけっこう気づきにくい。罠である。このエラーについて、公式のドキュメントを読むと以下のように書かれている。

True north is not available on this device. This usually indicates that the device’s location is not yet available.

訳すと「デバイスで真北がとれていない。たいていこれはデバイスの位置情報がまだ有効になっていないのが原因」といった感じ。これだけ読むと、タイミングが悪くて現在地がまだ取れてないから起こったのかな」と思ってしまうが、アプリ内でCoreLocationを使用して既に現在地の取得に成功しているような状況でも発生する。しかも同一ユーザが繰り返しこのエラーに遭遇する。

色々調べてみてわかったのだが、そういう状況は、iOSの設定の「プライバシー > 位置情報サービス > システム・サービス > コンパスの調整
(英語では “Privacy > Location Services > System Services > Compass Calibration“) がオフになっていると発生する。この項目、設定の奥深くにあるので、けっこう存在感が薄い。

「コンパスの調整を許可する」という言葉ではちょっとわかりにくいのだが、これは実際には「磁気偏角 (真北と磁北のズレ) へのアクセスを許可する」という項目だ。これがオフになっていると、真北 (true north) は取得できない。磁気偏角は地球上の位置によって変わるものであり、これが取れると現在地のヒントになる。なので、これは「位置情報関係のプライバシー設定」に分類されているのだ。

ちなみに、CoreLocationのAPIを使って真北を取ろうとするとどうなるか。真北はCLHeadingのプロパティである trueHeadingが真北なのだが、「コンパスの調整」がオフになっている場合、trueHeadingの値は-1になり、特にエラーは発生しない。