iOS 11から使えるようになったCoreNFCフレームワークとNetworkExtensionフレームワークのNEHotspotConfiguration機能を使って実現します。
Androidと同じような感覚でNFCの機能を完全に使えるわけではなかったり、NFC(中でもNDEF)に関する理解が必要など、一筋縄ではいかないところもありますが、 「iOSでAndroidと同じようなことできないの?」と言われることが多いと思われる (私も含めた)iOSアプリ開発者の方々の参考になればと思います。
事前準備
CoreNFCではNFCタグにデータを書き込むことはできません(後述のとおり、NDEFメッセージを読み取ることしかできない)。したがって、NFCタグへのNDEFデータ書き込みについては別の手段を用意する必要があります。
今回は下記AndroidアプリとNFCタグを使って書き込みを行いました。
- Androidアプリ: NFC Tools
- NFCタグ: MM-NFCT(サンワサプライ)
書き込みデータ
今回はWi-Fi設定情報を書き込みたいため、下図のように「Wi-Fi network」情報を書き込みました。この場合、NDEFにおけるType Name Format(以下、TNF)はmedia
、Typeはapplication/vnd.wfa.wsc
となります。
NetworkExtensionを使う
CapabilitiesにおいてHotspot Configuration
をONにして、NetworkExtension
をimportします。
下記はViewController.swiftに実装したサンプルコードです。
Wi-Fi接続(W-Fiコンフィギュレーション適用)用メソッドとしてconnect(_:passphrase:)
、切断(W-Fiコンフィギュレーション削除)用メソッドとしてdisconnect(_:)
を用意します。
今回はWPAを想定しているためisWEP
についてはfalse
に設定しています。また、アプリ画面がバックグラウンドにある場合であってもWi-Fi接続できるようにNEHotspotConfiguration
のjoinOnce
プロパティについては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
を追加します。
<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接続を実行してます。
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フレームワークの改善に寄与していきましょう😄
参考
- Qiita記事:【NFC】NDEFについて理解する
- 書籍:iOS 11 Programming