Help us understand the problem. What is going on with this article?

iOSでNFCタグを読み取ってWi-Fi接続させる

More than 1 year has passed since last update.

iOS 11から使えるようになったCoreNFCフレームワークとNetworkExtensionフレームワークのNEHotspotConfiguration機能を使って実現します。
Androidと同じような感覚でNFCの機能を完全に使えるわけではなかったり、NFC(中でもNDEF)に関する理解が必要など、一筋縄ではいかないところもありますが、 「iOSでAndroidと同じようなことできないの?」と言われることが多いと思われる (私も含めた)iOSアプリ開発者の方々の参考になればと思います。

NFC-WiFi.png

事前準備

CoreNFCではNFCタグにデータを書き込むことはできません(後述のとおり、NDEFメッセージを読み取ることしかできない)。したがって、NFCタグへのNDEFデータ書き込みについては別の手段を用意する必要があります。
今回は下記AndroidアプリとNFCタグを使って書き込みを行いました。

書き込みデータ

今回はWi-Fi設定情報を書き込みたいため、下図のように「Wi-Fi network」情報を書き込みました。この場合、NDEFにおけるType Name Format(以下、TNF)はmedia、Typeはapplication/vnd.wfa.wscとなります。

NFCTools.png

NetworkExtensionを使う

CapabilitiesにおいてHotspot ConfigurationをONにして、NetworkExtensionをimportします。
下記はViewController.swiftに実装したサンプルコードです。
Wi-Fi接続(W-Fiコンフィギュレーション適用)用メソッドとしてconnect(_:passphrase:)、切断(W-Fiコンフィギュレーション削除)用メソッドとしてdisconnect(_:)を用意します。
今回はWPAを想定しているためisWEPについてはfalseに設定しています。また、アプリ画面がバックグラウンドにある場合であってもWi-Fi接続できるようにNEHotspotConfigurationjoinOnceプロパティについてはfalseに設定しておきます(デフォルトはfalseのため、今回は設定する必要はありません)。

import UIKit
import NetworkExtension

class ViewController: UIViewController {

    var session: NFCNDEFReaderSession?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    private func connect(_ ssid: String, passphrase: String) {
        let hotspotConfig = NEHotspotConfiguration(ssid: ssid, passphrase: passphrase, isWEP: false)
        // hotspotConfig.joinOnce = false   // default

        NEHotspotConfigurationManager.shared.apply(hotspotConfig) { error in
            if let error = error {
                print("failed in applying Wi-Fi configuration, error: ", error.localizedDescription)
            } else {
                print("successful to apply Wi-Fi configuration")
            }
        }
    }

    private func disconnect(_ ssid: String) {
        NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid)
        print("removed Wi-Fi configuration")
    }
}

CoreNFCを使う

CapabilitiesにおいてNear Field Communication Tag ReadingをONにして、CoreNFCをimportします。また、下記の通りInfo.plistにNFCReaderUsageDescriptionを追加します。

Info.plist
<key>NFCReaderUsageDescription</key>
<string>This application uses NFC feature.</string>

NDEFのパース

現実問題として、CoreNFCを使いこなすには、NDEFに関する仕様を理解(1)した上で、そのバイナリデータを適切に解析処理(2)する必要があります。が、後者(2)についてはOSSであるVYNFCKitを利用することでだいぶ負担軽減ができます。コード自体はObjective-Cで書かれており、Objective-C/Swiftの両方で利用可能で、CocoaPods/Carthageに対応しています。

完成サンプルコード

コードは多少見づらくなりますが、上記のViewControllewr.swiftにNFCタグを読み取り、Wi-Fi接続する処理を追加した完成コードが下記になります。
IBActionとして追加したscanTag(_:)メソッドでNFCタグ読み取りの処理を開始、デリゲート設定しています。
コールバックされるreaderSession(_:didDetectNDEFs:)メソッドにおいて、今回NFCタグに書き込んだWi-Fi設定データをパースする処理を実装しています。
VYNFCNDEFPayloadParser.parseで1次パース処理を行い、VYNFCNDEFWifiSimpleConfigPayloadおよびVYNFCNDEFWifiSimpleConfigCredentialで必要となるWi-Fi情報を取得しています。
今回は簡易的にSSIDとパスフレーズのみを取得した上で、connect(_:passphrase:)メソッドを呼び出し、Wi-Fi接続を実行してます。

ViewController.swift
import UIKit

import NetworkExtension

import CoreNFC
import VYNFCKit

class ViewController: UIViewController {

    var session: NFCNDEFReaderSession?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func scanTag(_ sender: UIButton) {
        session = NFCNDEFReaderSession(delegate: self, queue: .global(), invalidateAfterFirstRead: true)
        session?.alertMessage = "Keep iPhone close to the tag."
        session?.begin()
    }

    private func connect(_ ssid: String, passphrase: String) {
        let hotspotConfig = NEHotspotConfiguration(ssid: ssid, passphrase: passphrase, isWEP: false)
        // hotspotConfig.joinOnce = false   // default

        NEHotspotConfigurationManager.shared.apply(hotspotConfig) { error in
            if let error = error {
                print("failed in applying Wi-Fi configuration, error: ", error.localizedDescription)
            } else {
                print("successful to apply Wi-Fi configuration")
            }
        }
    }

    private func disconnect(_ ssid: String) {
        NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid)
        print("removed Wi-Fi configuration")
    }
}

extension ViewController: NFCNDEFReaderSessionDelegate {

    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        for message in messages {
            print("\(#function) - NFC messages count: \(messages.count)")

            for record in message.records {
                guard let parsedPayload = VYNFCNDEFPayloadParser.parse(record) else {
                    continue
                }
                if let wifi = parsedPayload as? VYNFCNDEFWifiSimpleConfigPayload {
                    for credential in wifi.credentials {
                        switch credential {
                        case let wificonfig as VYNFCNDEFWifiSimpleConfigCredential:
                            print("ssid: \(wificonfig.ssid)")
                            print("passphrase: \(wificonfig.networkKey)")
                            DispatchQueue.main.async {
                                self.connect(wificonfig.ssid, passphrase: wificonfig.networkKey)
                            }
                        default:
                            break
                        }
                    }
                }
            }
        }
    }

    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        print("\(#function) - error: \(error.localizedDescription)")
    }
}

最後に

いかがでしたでしょうか?CoreNFCを使いこなすにはNFC、NDEFに関する理解が不可欠です。
手始めにはQiita記事: 【NFC】NDEFについて理解するあたりが参考になるのではないかと思います。また、CoreNFCについては書籍: iOS 11 Programmingが参考になりました。
皆さん、最新機能を使いながら開発者同士での情報共有、iOSフレームワークの改善に寄与していきましょう😄

参考

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away