iPhone搭載センサの種類と使われ方集 + デモアプリ

# はじめに

iPhoneには多くのセンサが内臓されていますが、どんな情報にアクセスできるかご存知ですか👀
私自身、センサを使ったアプリを作成するのですが、まだまだ知らなかったプロパティもありましたので、改めてここで整理したいと思います。

ということで、

**1. どんなセンサ・プロパティにアクセスできるのか**
**2. どんな用途に利用されているのか**

これらを、**デモアプリを作りながら確認**していきたいと思います。

それではみていきましょう！


# 前提条件
デモアプリでは以下の環境で動作を確認しています

- iOSバージョン: `14.2`
- 端末: `iPhone 12 Pro`
- デモアプリの実装言語: `Swift 5`

# 一覧 
| 項番 | ページ内リンク |
|:----:|:-------------|
| 1 | [LiDARセンサ](#lidarセンサ) |
| 2 | [モーションセンサ](#モーションセンサ) |
| 3 | [位置情報/GPSセンサ](#位置情報gpsセンサ) |
| 4 | [歩数センサ (Pedometer)](#歩数センサ) |
| 5 | [標高/気圧センサ](#標高差気圧センサ) |
| 6 | [輝度センサ](#輝度センサ) |
| 7 | [近接センサ](#近接センサ) |
| - | [デモアプリについて](#デモアプリについて) |


# 各種センサ 

## LiDARセンサ

#### 概要

LiDAR は「Light Detection and Ranging」の略で、赤外線が対象物に反射して戻ってくるまでの時間を計測することにより、物体との距離を測る事ができます。
iPad Pro 第四世代、iPhone 12 Pro、Pro Maxから搭載された新しいセンサです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/5a749f1c-949d-ddd7-4000-1e65828bbfee.png" width="300">

#### 取得できる値

|取得できる値|説明|
|----|----|
|デプスデータ|物体との距離情報をピクセル毎に取得できる|

#### 使用例

`カメラ`撮影時、暗所でオートフォーカスの高速化、暗い場所での撮影時でも背景を良い感じにボケさせるナイトポートレート写真の撮影などに活用されています。
最近では、LiDARを活用した部屋のスキャンが可能なアプリも登場しています。

<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit
import ARKit
import RealityKit

class LiDARViewController: UIViewController {
    
    @IBOutlet weak var arView: ARView!
    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureARView()
    }
    
    func configureARView() {
        arView.session.delegate = self
        arView.automaticallyConfigureSession = false
        let configuration = ARWorldTrackingConfiguration()
        configuration.sceneReconstruction = .mesh
        // sceneDepthオプションの利用可否チェック
        if type(of: configuration).supportsFrameSemantics(.sceneDepth) {
            // .sceneDepthオプションの指定でLiDAR由来のDepthデータが利用できる
            configuration.frameSemantics = .sceneDepth
        }
        arView.session.run(configuration)
    }
}

extension LiDARViewController: ARSessionDelegate {
    func session(_ session: ARSession, didUpdate frame: ARFrame) {
        guard let depthData = frame.sceneDepth else { return }
        let ciDepthMapImage = CIImage(cvPixelBuffer: depthData.depthMap)
        let orientation :CGImagePropertyOrientation = CGImagePropertyOrientation.right
        let orientedCIImage = ciDepthMapImage.oriented(orientation)
        imageView.image = UIImage(ciImage: orientedCIImage)
    }
}

```


※`Info.plist` に `Privacy - Camera Usage Description` の記載が必要です。
※LiDARセンサ搭載のiPhone, iPadのみ動作します

</div></details>

<details><summary>参考</summary><div>
[Visualizing and Interacting with a Reconstructed Scene](https://developer.apple.com/documentation/arkit/world_tracking/visualizing_and_interacting_with_a_reconstructed_scene)
[ARKit 4のLiDAR Depth API #WWDC20 #ARKit4 #iOS14｜shu223｜note](https://note.com/shu223/n/nabb1450082b9#9FUle)
[[ARKit]LiDARスキャナを使ったアプリを動かしながら実装を見る](https://dev.classmethod.jp/articles/lidar_scanner_arkit_intro/)

</div></details>

## モーションセンサ

#### 概要

加速度センサ、ジャイロスコープ、地磁気センサなどを使って、本体の加速度や傾きを取得できます。
ユーザの動きを測るのに重宝する便利なセンサ達です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/8ce8336a-37f4-fc1a-2f5c-cafe5c8fd16d.png" width="300">

#### 取得できる値

|  取得できる値  | 単位 | 説明 |   
| ---- | ---- | ---- | 
|  加速度 (X/Y/Z)  | G | 端末の受ける加速度 |
|  姿勢 (ピッチ/ロール/ヨー)  | N/A | 端末の向き |
|  地磁気 (X/Y/Z)  | N/A | 磁気を受ける向き |
|  重力 (X/Y/Z)  | G | 重力を受ける向き |

#### 使用例

`スマホ画面の自動回転`、`カメラアプリでの端末の向き`判断などに、傾き情報が使われています。
他にも`ナビアプリでの案内`や、`端末を動かして遊ぶゲームアプリ`など挙げればキリがない程、様々なところで使われています。
また、歩数は専用のセンサ(Pedometer)から取得できますが、リアルタイムに一歩を検出したい場合は加速度センサを使うということもあります。[(リアルタイムに一歩を検知するサンプル)](https://github.com/rirex5/RealtimeStepCounterForiOS))

<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit
import CoreMotion


class MotionViewController: UIViewController {

    var motionManager: CMMotionManager!
    
    var motionInfomations: [KeyValue] = [KeyValue(key: "", value: "値の取得中...")]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureMotionManager()
    }
        
    private func configureMotionManager() {
        motionManager = CMMotionManager()
        guard let motionManager = motionManager else { return }
        // motionManager.deviceMotionUpdateInterval = 1
        if motionManager.isDeviceMotionAvailable {
            motionManager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler: { [weak self] (motion: CMDeviceMotion?, error: Error?) in
                guard let motion = motion else { return }
                self?.setMotionInfomation(motion)
            })
        }
    }

    func setMotionInfomation(_ deviceMotion: CMDeviceMotion) {
        print(deviceMotion.userAcceleration.x)
        print(deviceMotion.userAcceleration.y)
        print(deviceMotion.userAcceleration.z)
        print(deviceMotion.attitude.pitch)
        print(deviceMotion.attitude.roll)
        print(deviceMotion.attitude.yaw)
        print(deviceMotion.rotationRate.x)
        print(deviceMotion.rotationRate.y)
        print(deviceMotion.rotationRate.z)
        print(deviceMotion.gravity.x)
        print(deviceMotion.gravity.y)
        print(deviceMotion.gravity.z)
    }
}

```

</div></details>


[CMMotionManager](https://developer.apple.com/documentation/coremotion/cmmotionmanager)


## 位置情報/GPSセンサ
#### 概要

GPSデータを使って、緯度・経度情報など位置や移動に関する情報を取得できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/12db1c1a-59e8-1719-732e-cac9b713d831.png" width="300">

#### 取得できる値

|  取得できる値  | 単位 | 説明 |   
| ---- | ---- | ---- | 
|  緯度・経度  | N/A | GPSから求められる現在地の緯度・経度 |
|  標高  |  メートル | GPSから求められる標高 |
|  移動速度  | メートル / 秒 | GPSから求められる端末の移動速度 |
|  階数  | 階 | 現在の高さが何階相当であるのか |
|  出発/到着時刻  | N/A | 現在地にどのくらい滞在したか |

#### 使用例
`マップ`、`位置情報ゲーム`、`ランニング記録アプリ`、`お店のクーポンアプリ`など、あらゆるアプリに使われてますね！
移動速度・標高なども標準で取得できるため、`スピードメータアプリ`や`高度計アプリ`なんかも存在します。
個人的に階数というプロパティが用意されていることに驚きましたが、このプロパティどのくらい正確でどんなアプリに使われているのだろうか...。

<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit
import CoreLocation

class LocationViewController: UIViewController {

    var locationManager: CLLocationManager!

    override func viewDidLoad() {
        super.viewDidLoad()
        configureLocationManager()
    }
    
    private func configureLocationManager() {
        locationManager = CLLocationManager()
        guard let locationManager = locationManager else { return }
        locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = self
        locationManager.startUpdatingLocation()
    }

    // 位置情報が更新される度に呼ばれる
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.first else { return }
        print(location)
    }
    
    func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
        print(visit)
    }
}
```

※ `Info.plist` に `Privacy - Location When In Use Usage Description` の記載が必要です。

</div></details>

[CLLocationManager](https://developer.apple.com/documentation/corelocation/cllocationmanager)


## 歩数センサ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/c5f93367-afd9-a7da-f8e4-f7f24b1ee578.png" width="300">

#### 概要

歩数に加えて、色々なウォーキングデータを取得できます。
加速度センサではなく、常時歩数を記録するための省電力なセンサ「モーションコアプロセッサ」を使って値が取得されているのが特徴になります。

#### 取得できる値

|  取得できる値  | 単位 | 説明 |   
| ---- | ---- | ---- | 
|  歩数  | 歩 |  |
|  距離  | メートル | ユーザが移動した推定距離 |
|  瞬間ペース  | メートル / 秒 |  |
|  平均ペース  | メートル / 秒 |  |
|  ケイデンス  | 歩 / 秒 | 1秒毎の歩数 |
|  登った階数  | 階 | 歩いて登ったおおよその階数 |
|  降った階数  | 階 | 歩いて降ったおおよその階数 |

#### 使用例

`ヘルスケアアプリ`や、`ランニングの記録アプリ`などで主に利用されています。


<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit
import CoreMotion

class PedometerViewController: UIViewController {

    var pedometer: CMPedometer!    
    var motionInfomations: [KeyValue] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configurePedometerManager()
    }

    private func configurePedometerManager() {
        pedometer = CMPedometer()
        guard let pedometer = pedometer else { return }
        if CMPedometer.isStepCountingAvailable() {
            
            // 1000秒前から現在までの値を取得する
            let from = Date(timeIntervalSinceNow: -1000)
            
            pedometer.startUpdates(from: from, withHandler: { [weak self] (pedometerData: CMPedometerData?, error: Error?) in
                guard let pedometerData = pedometerData else { return }
                DispatchQueue.main.async {
                    self?.setPedometerInfomation(pedometerData)
                }
                
            })
        }
    }

    func setPedometerInfomation(_ pedometerData: CMPedometerData) {
        print(pedometerData.numberOfSteps)
        print(pedometerData.distance)
        print(pedometerData.averageActivePace)
        print(pedometerData.currentPace)
        print(pedometerData.currentCadence)
        print(pedometerData.floorsAscended)
        print(pedometerData.floorsDescended)
   }
}


```

※ `Info.plist` に `Privacy - Motion Usage Description` の記載が必要です。

</div></details>

[CMPedometer](https://developer.apple.com/documentation/coremotion/cmpedometer)

## 標高差/気圧センサ

#### 概要

気圧センサが内臓されており、計測開始時点からの標高の差を取得する事ができます。
これによって、GPSで計測した緯度経度の2次元情報に気圧によって高さの情報を加える事ができ、3次元的に現在地を取得する、なんて事が可能になっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/0086e3fe-cc53-f83c-77ce-71d8bc2b1da9.png" width="300">

#### 取得できる値

|  取得できる値  | 単位 | 説明 |   
| ---- | ---- | ---- | 
|  高度  | メートル | 最初に記録された高度を0mとした時の、相対的な高度(絶対高度ではない) |
|  気圧  | kPa |  |

#### 使用例

直接的には`気圧計アプリ`や`高度計アプリ`で利用されています。
車の`ナビゲーションアプリ`でも使われており、一般道と高架の判定に用いられています。

余談ですが、昨年のアドベントカレンダーでは[気圧計を使って天気を予想する](https://qiita.com/AtsushiOtsubo/items/06079f07af637f9867ad)なんて使い方もしていました。


<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit
import CoreMotion

class AltimeterViewController: UIViewController {

    var altimeter: CMAltimeter!
    var altimeterInfomations: [KeyValue] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureAltimeterManager()
    }
    
    private func configureAltimeterManager() {
        altimeter = CMAltimeter()
        guard let altimeter = altimeter else { return }
        if CMAltimeter.isRelativeAltitudeAvailable() {
            
            altimeter.startRelativeAltitudeUpdates(to: OperationQueue.current!, withHandler: { [weak self] (altitudeData: CMAltitudeData?, error: Error?) in
                guard let altitudeData = altitudeData else { return }
                self?.setAltimeterInfomation(altitudeData)
            })
        }
    }

    func setAltimeterInfomation(_ altitudeData: CMAltitudeData) {
        print(altitudeData.relativeAltitude)
        print(altitudeData.pressure)
    }
}

```

</div></details>

[CMAltimeter](https://developer.apple.com/documentation/coremotion/cmaltimeter)

## 輝度センサ

#### 概要

周囲の明るさと連動してスクリーンの輝度が変化するため、スクリーンの輝度を介して周囲の明るさを取得する事ができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/a4f2a616-aba3-d66a-e0d8-d3c7026110fd.png" width="300">

#### 取得できる値

|  取得できる値  | 説明 |   
| ---- | ---- |
|  輝度  | 0.0(暗い) 〜 1.0(明るい)の範囲で取得できる |

#### 使用例

そのままですが`画面の明るさの自動調整`に使われています。
このセンサ、面白い使われ方がされていまして、夜道の明るさを調査しようという研究[^1]にも使われています。
[^1]: スマートフォン搭載照度センサの個体差に対応した夜道における街灯照度推定値校正手法の提案, https://ci.nii.ac.jp/naid/170000130857/


<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit

class BrightnessViewController: UIViewController {
    
    var brightnessInfomations: [KeyValue] = [KeyValue(key: "", value: "値の取得中...")]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureBrightness()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        NotificationCenter.default.removeObserver(self,
                                                  name: UIScreen.brightnessDidChangeNotification,
                                                  object: nil)
    }

    private func configureBrightness() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(brightnessChanged(_:)),
                                               name: UIScreen.brightnessDidChangeNotification,
                                               object: nil)
    }
    
    @objc func brightnessChanged(_ notification: Notification) {
        if let screenObject = notification.object {
            let brightness = (screenObject as AnyObject).brightness
            print(brightness)
        }
    }
}

```

</div></details>

[brightnessDidChangeNotification](https://developer.apple.com/documentation/uikit/uiscreen/1617832-brightnessdidchangenotification)

## 近接センサ

#### 概要

物体との近接状態を取得できる。(ディスプレイ側)
手元で試してみたところ、約4cm前後で近接/非近接の判定がされている模様。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/f4b2cb8e-1293-99c9-705f-0e52584fd62b.png" width="300">


#### 取得できる値

|  取得できる値  | 説明 |   
| ---- | ---- |
|  近接しているか  | 近接しているかが、Bool値で返される。 |

#### 使用例

通話中に端末を耳に当てている時は画面の操作をロックするという事が主な用途です。
実は、`腕立て伏せ記録アプリ`でも利用されていて、端末を床に起き、顔が近づくことを検知して1回の腕立てを検知するというユニークな使われ方をしています。

<details><summary>サンプルコードはこちら</summary><div>

```swift
import UIKit

class ProximityViewController: UIViewController {
    
    var proximityInfomations: [KeyValue] = [KeyValue(key: "", value: "値の取得中...")]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureProximity()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        NotificationCenter.default.removeObserver(self,
                                                  name: UIDevice.proximityStateDidChangeNotification,
                                                  object: nil)
    }
    
    private func configureProximity() {
        UIDevice.current.isProximityMonitoringEnabled = true
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(proximityChanged(_:)),
                                               name: UIDevice.proximityStateDidChangeNotification,
                                               object: nil)
    }
    
    @objc func proximityChanged(_ notification: Notification) {
            let proximityState = (uiDeviceObject as AnyObject).proximityState ?? false
            print(proximityState)
        }
    }
}

```

</div></details>

[proximityStateDidChangeNotification](https://developer.apple.com/documentation/uikit/uidevice/1620046-proximitystatedidchangenotificat)


## デモアプリについて
上記のセンサが動作する[デモアプリ](https://github.com/rirex5/iPhoneSensors)をGitHubに公開していますので、何らかのセンサの値を利用される場合の参考になれば幸いです。こちら、適宜アップデートを入れていきたいと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136288/d72adb2b-91ed-b1a9-7dee-4ee4a0ae41e7.png" width="250">


# おわりに
スマートフォンは様々な入出力に富んでおり、これらを使うとユニークな体験を届けることができるということがシェアできれば幸いです。
便利なWebサービス・APIもどんどん登場していますが、オフラインである端末内臓センサも上手く活用すると、面白いものづくりができそうですね！

また [DeNA 公式 Twitter アカウント @DeNAxTech](https://twitter.com/DeNAxTech) では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローして下さい！
<a href="https://twitter.com/DeNAxTech?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-size="large" data-lang="ja" data-show-count="false">Follow @DeNAxTech</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>