iOS11 Core NFCを使ってみた

iOS11になって、Core NFCというフレームワークが追加されました。
これはNFC(近距離無線通信)を利用したタグとの通信が出来るフレームワークです。

現状では読み取り専用で、しかも専用のアプリを起動しなければいけないそうです。
Androidアプリでは「NFCタグを端末に近づけるとアプリを起動する」といったことができますが、iPhoneではまだそのようなバックグラウンドからNFCの検知イベントでアプリを立ち上げるなどはできません。

ですが、長年さまざまな人が待望した機能ではあります。
実験する価値はあると思っています。

今回はCore NFCを使ったサンプルのアプリの紹介です。
コード量はすくないですが、ハマリポイントもあったので皆さんのお役に立てればと思います。


# 動作環境

* Xcode9.2
* iPhoneX iOS 11.1.2

# NFCタグをゲットする
Core NFCを利用したいなら兎にも角にも最初はNFCタグをどこからか入手しなければ行けません。
幸いAmazonで10枚入りのモノが売っていたのでそちらを購入します。


[サンワサプライ NFCタグ(10枚入り) 白 MM-NFCT](https://www.amazon.co.jp/gp/product/B00GXSGL5G/ref=oh_aui_detailpage_o00_s00?ie=UTF8&psc=1)

> 2017/12/17現在、870円ととても安くなっています。

# AndroidでNFCタグにデータを書き込む

残念なことに、現在Core NFCはNFCタグのデータを読み込むことはできても書き込むことはできません。
「サンワサプライ NFCタグ」はデータがない状態で販売されているので何かしらの方法でデータを書き込むことが必要です。


一番簡単なのがAndroidのアプリを使うことです。
私は[NFC Tools](https://play.google.com/store/apps/details?id=com.wakdev.wdnfc&hl=ja)というアプリを使って書き込みをしてみました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31587/42f41993-d453-69d6-89d3-2ef357474f58.png)


Writeタブの「Add a record」から書き込むデータを指定して「Write / 30 Bytes」セルをタップ、書き込みたいNFCタグを近づけるとデータを書き込めます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31587/51af1867-3e87-baa8-13df-b51bf98f8409.png)

# iOS側の実装

NFCタグを使うには次の段階を踏む必要があります。

* Info.plistに`NFCReaderUsageDescription`キーを追加
* ケイパビリティをONにする
* NFC読み取りの実装をする

## Info.plistに`NFCReaderUsageDescription`キーを追加
NFCの利用は位置情報やカメラの利用と同じようにユーザーに許可を求める必要があります。
Info.plistに次のKeyと説明文言を追加します。

```xml
<key>NFCReaderUsageDescription</key>
<string>[ユーザーへアプリがNFCを利用する説明を記述]</string>
```

Xcodeのプロパティリスト表示では`Privacy - NFC Scan Usage Description`というキーを追加すればOKです。

<img width="500" alt="スクリーンショット 2017-12-16 23.33.54.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/31587/a68f8959-8f42-ded3-9e8a-6ffa9b1aaccc.jpeg">

# ケイパビリティをONにする
続いてケイパビリティ`Near Field Communication Tag Reading`をONにします。


<img width="749" alt="スクリーンショット 2017-12-16 23.36.00.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/31587/741cc8df-541f-d024-e2a3-f0356b90326c.jpeg">

エンタイトルメントファイルが追加されます。

これで認証系の事前準備は終了です。
次からコードを書いていきます。

# NFC読み取りの実装をする

いよいよ実装していきます。
ViewControllerに実装していきたいと思います。

`CoreNFC`フレームワークをインポートして、`NFCNDEFReaderSessionDelegate`プロトコルを適応させます。

```swift
import UIKit
import CoreNFC

class ViewController: UIViewController, NFCNDEFReaderSessionDelegate {
    var session: NFCNDEFReaderSession?
```

## 読み取り開始
```swift
    //読み取りボタンを押した
    @IBAction func readNFC(_ sender: UIButton) {
        if NFCNDEFReaderSession.readingAvailable {
            session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
            session?.alertMessage = "NFCタグをiPhoneに近づけてください"
            session?.begin()
        } else {
            print("NFCが使えません")
        }
    }
```

NFCが使える端末かどうかは`NFCNDEFReaderSession.readingAvailable`で判定ができます。trueなら利用できfalseなら利用できません。
現在iPhone7/7Plus、iPhone8/8Plus、iPhoneXで利用ができます。

`NFCNDEFReaderSession`のイニシャライズでNFC読み取りのセッションを作成します。
第三引数の`invalidateAfterFirstRead`は連続でNFCタグを読み取るかどうかのフラグです。trueなら最初に読み取った時点でセッションを終了します。falseならタイムアウトするまで連続で読み取ります。

`session?.alertMessage`でシステムが表示した読み取りUIの説明文言を指定できます。

`session?.begin()`で読み取り開始です。

システムが用意する読み取りUIが表示されます。

![IMG_0079.jpg](https://qiita-image-store.s3.amazonaws.com/0/31587/2ebdc8cf-0c86-d92e-69e1-448e2e3e295c.jpeg)

赤く丸をつけたところが`session?.alertMessage`で指定した文字列です。

## デリゲートを受け取る

`NFCNDEFReaderSessionDelegate`は２つのメソッドを実装しなければいけません

`readerSession(_:didInvalidateWithError:)`と`readerSession(_:didDetectNDEFs:)`です。

`readerSession(_:didInvalidateWithError:)`はエラーが発生した場合に呼ばれます。
ユーザーがシステムの読み取りUIの「キャンセル」を押すか読み取りがタイムアウト（現状60秒）しても呼ばれます。

```swift
    /// 読み取りエラーが起こった時呼ばれる。ユーザーがキャンセルボタンを押すか、タイムアウトしたときに呼ばれる。
    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        print("error:\(error.localizedDescription)")
    }
```

NFCが無事に読み取れると`readerSession(_:didDetectNDEFs:)`が呼ばれます。


```swift
/// 読み取りに成功したら呼ばれる。
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {

    for message in messages {
        for record in message.records {
            print(String(data: record.payload, encoding: .utf8)!)
        }
    }
}
```

NFCの情報は`message`引数から`NFCNDEFMessage`の配列として受け取れます。通常は１つのタグにメッセージは１つだそうです。

`NFCNDEFMessage`インスタンスは`var records:[NFCNDEFPayload]`を持っています。
NFCタグには１つまたは複数のレコードを含みます。

この`recode`の`payload`プロパティ値を読み取ればOKです。

#実際にやってみた。

Androidアプリ「NFC Tools」から自分のブログのURLである[blog.personal-factory.com](http://blog.personal-factory.com)のデータを書き込みます。

iOSでアプリをビルドしてタグを近づけるとUIがチェックマークに変わり、タグのデータが出力されました

![IMG_841EAA24B1EA-1.jpeg](https://qiita-image-store.s3.amazonaws.com/0/31587/814787fd-c098-5f55-906f-0b209d9b8a17.jpeg)

出力文字列

```
blog.personal-factory.com
```

成功ですね。

# ハマったこと

物品管理として使おうかと思ったので、社内のテスト端末であるiPhone5cにNFCタグを貼り付けたのです。
紹介したNFCタグはシールとなっていて、どこでも貼り付けられるようになっています。
ところが、一行に認識せず、タイムアウトエラーが出てしまっていました。

１時間悩んで、他のタグでやると今度は認識しました。
どうやら金属に貼り付けると認識できないことがあるようです。
試しにどこにも貼り付けないで近づけると認識しました。

このNFCタグの特徴なのか、iPhoneXの精度の問題なのかはまだ確認していないです。

ビジネスとして物品管理などを行う場合は、他のメーカーのNFCタグも試して、精度を確認した方がいいかと思いました。