見出し画像

U1チップによる空間認識 - Nearby Interaction Framework

iOS 14で新たに追加されたNearby Interactionは、U1チップを搭載したiPhone同士でインタラクションを行うためのフレームワーク。

インタラクションを行う、と言われてもピンとこないかもしれないが、U1は「空間認識」を行えるチップなので、それにより互いに相手の距離と方向がわかる。で、その情報を使って相互になんかしましょうというもの。

画像2

以下、WWDC 2020のセッション、"Meet Nearby Interaction"を見たメモ。


2020年6月時点での対応端末

iPhone 11シリーズに加え、LiDARを搭載する新型iPad ProはU1チップも搭載しているかと適当に考えていたが、非搭載。つまり今のところ対応端末はiPhone 11系のみ

シミュレータでも動作確認可能

そんなわけでNearby Interactionを実機で試すには最低でも2台のiPhone 11が必要で、個人ではなかなかその状況を用意するのは難しい。

それを見越してか、Nearby Intearactionはシミュレータでも動作する

ただし、画面上ではあくまで2次元なので、「空間認識」の醍醐味である3次元的なインタラクションを体感するにはやはり実機で試したいところ...

最小実装

インタラクション開始にあたってどんなやりとりが必要なのか、何ができるのか、どんな情報が得られるのか、という大枠を理解するにはソースコードを見たほうが理解が早い。

サポートしているかの確認

guard NISession.isSupported else {
   print("Nearby Interaction is not available on this device.")
   return
}

セッションオブジェクトの作成

niSession = NISession()
niSession?.delegate = self

delegateプロパティにセットするオブジェクトはNISessionDelegateプロトコルに準拠している必要がある(が、すべてのメソッドがoptionalなので実装がなくても動作はする)

ディスカバリトークンを生成し、交換する

guard let myToken = niSession?.discoveryToken else { return }

if let encodedToken = try? NSKeyedArchiver.archivedData(withRootObject: myToken, requiringSecureCoding: true) {
   <# share token using your app's networking layer #>
}

ここはちょっと解説が必要だと思うので、WWDC 2020のセッションより該当箇所を抜粋する:

So let's say that there are two or more users running your app, and they want to interact with one another in some spatial manner. Before this can happen, your app needs to let the system know, on both sides, how to identify the other device when it is nearby. 
(例えば、アプリを実行しているユーザーが2人以上いて、空間的な方法でお互いにインタラクトしたいとします。これを実現する前に、アプリは、相手のデバイスが近くにあるときに、相手のデバイスを識別する方法をシステムに知らせる必要があります。)

Devices can discover each other in a privacy-preserving manner by using something we call a discovery token. A discovery token is a randomly-generated identifier for a given device in a particular Nearby Interaction session. The discovery token has a limited time period for which it can be used, and that time period is exactly as long as the session itself, meaning that if you invalidate the session, or if the user exits your app, any session and its associated token get invalidated. The token is generated by the system, and you receive it in your application through the session object. Each session you create has its own unique associated discovery token. And finally, your app needs to copy the discovery token from the session object and then share it between users that want to interact.
(デバイスは、ディスカバリ トークンと呼ばれるものを使用することで、プライバシーを保護した方法でお互いを発見することができます。ディスカバリトークンは、特定のニアインタクションセッション内のデバイスのためにランダムに生成された識別子です。つまり、セッションを無効にしたり、ユーザーがアプリを終了したりすると、すべてのセッションとそれに関連するトークンが無効になります。トークンはシステムによって生成され、アプリケーションではセッションオブジェクトを通して受け取ります。作成した各セッションには、固有の関連するディスカバリトークンがあります。そして最後に、アプリはセッション オブジェクトからディスカバリトークンをコピーして、対話を希望するユーザー間で共有する必要があります。)

抜粋が長くなってしまったが、インタラクションする相手を識別する手段として「ディスカバリトークン」(クラスとしてはNIDiscoveryToken)なるセッションごとに一時的に生成するトークンを交換しあう、という話。

で、このトークンを交換する部分、上のコードでは`<# share token using your app's networking layer #>`とプレースホルダーで表記されているが、どういうことかというと、このディスカバリ・トークンの交換のための通信はNearby Interactionフレームワークでは面倒をみない。ちなみにサンプルコードではMultipeerConnectivityフレームワークを使用している。

・・・というかこのへんで気付いたが、そもそもNearby Interactionフレームワークでは通信(データの交換)は行わない。あくまでU1の空間認識で互いの位置・方角がわかるよ、通信は別でやってね、という設計のようだ。

受け取ったトークンをデコード

guard let peerDiscoveryToken = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NIDiscoveryToken.self, from: peerTokenData) else {
   print("Unexpectedly failed to decode discovery token.")
   return
}

NINearbyPeerConfigurationオブジェクトを作成

デコードした相手のNIDiscoveryTokenオブジェクトをイニシャライザの引数に渡してNINearbyPeerConfigurationを初期化する。

let config = NINearbyPeerConfiguration(peerToken: peerDiscoveryToken)

### セッション開始

NISessionのrunメソッドの引数にNINearbyPeerConfigurationオブジェクトを渡してセッションを開始する。

niSession?.run(config)

以上が最小実装。

NISessionDelegateの実装

前述の最小実装によりNearby Interactionのセッションを開始できたとはいえ、アプリケーションとして何の機能もない。実際のインタラクションを実装するには、NISessionDelegateプロトコルに定義されているデリゲートメソッド群に実装を与えていく必要がある。

まず、近接オブジェクトに更新があると呼ばれるのが下記メソッド:

func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject])

nearbyObjects引数よりNINearbyObjectオブジェクトの配列が得られる。

1台のiPhoneは複数のセッションを同時に張れるため、特定のpeerのNINearbyObjectオブジェクトを取り出すには次のようにディスカバリトークンを見る。

// Find the right peer.
let peerObj = nearbyObjects.first { (obj) -> Bool in
   return obj.discoveryToken == peerToken
}

guard let nearbyObjectUpdate = peerObj else {
   return
}

NINearbyObject

NINearbyObjectクラスは次のようにdistanceプロパティから距離を、directionプロパティから3次元の方向を得られる。

extension NINearbyObject {

   public var distance: Float? { get }

   public var direction: simd_float3? { get }
}

ARKitとの併用は可能か?

空間を正しく認識すると聞いて思いつくのがARKitと組み合わせることだが、実装を見る限りARKitとバッティングするような部分はないので、併用は可能だと思われる(実機が手元に2台ないので試せてない)

バックグラウンドでも動作するか?

WWDCセッションでの次の言及から、フォアグラウンドでのみ動作すると思われる:

Whenever there are conditions preventing your session from running, your delegate will get a suspension notification. The session will be suspended, for example, when your app is no longer in the foreground.
(セッションの実行を妨げる条件があるときはいつでも、デリゲートは一時停止通知を受け取ります。例えば、アプリがフォアグラウンドでなくなったときなど、セッションが中断されます。)


ここから先は

0字
昨年は書籍という形にまとめましたが、今年はこのマガジンに集約することにしました。 最初は記事が少ないので格安から開始して、充実してくるごとに本来あるべき価格に戻していく予定です。というわけで早いうちに買うと非常にお得です。 昨年の書籍は約80ページ+本に載せなかった事項もたくさん、ということで最終的にはそれなりの量になるのではと思います。

堤がWWDC 2020およびiOS 14についてセッションやサンプルを見つつ勉強したことを記事にしていくマガジンです。NDAの都合上、Ap…

最後まで読んでいただきありがとうございます!もし参考になる部分があれば、スキを押していただけると励みになります。 Twitterもフォローしていただけたら嬉しいです。 https://twitter.com/shu223/