🕙
Core NFCでNDEFを読み書き(SwiftUI版)
はじめに
前回はデリゲートがめんどくさそうだったので UIKit でやったのですが SwiftUI でも簡単にできそうだったので SwiftUI でやってみました。
完成品はこんな感じ。
入力したテキストと固定値の URL を書き込んでいます。
ソース
import Foundation
import CoreNFC
import SwiftUI
final class NFCSession: NSObject, ObservableObject {
private var session: NFCNDEFReaderSession!
private var isWriting = false
private var ndefMessage: NFCNDEFMessage!
private var writeHandler: ((Error?) -> Void)?
private var readHandler: ((String?, Error?) -> Void)?
func startWriteSession(text: String, writeHandler: ((Error?) -> Void)?) {
self.writeHandler = writeHandler
isWriting = true
let textPayload = NFCNDEFPayload(
format: NFCTypeNameFormat.nfcWellKnown,
type: "T".data(using: .utf8)!,
identifier: Data(),
payload: text.data(using: .utf8)!)
let uriPayload = NFCNDEFPayload.wellKnownTypeURIPayload(url: .init(string: "https://www.am10.blog/")!)
ndefMessage = NFCNDEFMessage(records: [textPayload, uriPayload!])
startSession()
}
func startReadSession(readHandler: ((String?, Error?) -> Void)?) {
self.readHandler = readHandler
isWriting = false
startSession()
}
private func startSession() {
guard NFCNDEFReaderSession.readingAvailable else {
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
session.alertMessage = "スキャン中"
session.begin()
}
}
extension NFCSession: NFCNDEFReaderSessionDelegate {
// 必須
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
}
// 必須
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
}
// 必須ではないけどコンソールになんかでる
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
}
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
let tag = tags.first!
session.connect(to: tag) { error in
tag.queryNDEFStatus() { [unowned self] status, capacity, error in
if self.isWriting {
// 書き込み
if status == .readWrite {
self.write(tag: tag, session: session)
return
}
} else {
// 読み込み
if status == .readOnly || status == .readWrite {
self.read(tag: tag, session: session)
return
}
}
session.invalidate(errorMessage: "タグがおかしいよ(´∇`)")
}
}
}
private func write(tag: NFCNDEFTag, session: NFCNDEFReaderSession) {
tag.writeNDEF(self.ndefMessage) { [unowned self] error in
session.alertMessage = "書き込み完了♬(ノ゜∇゜)ノ♩"
session.invalidate()
DispatchQueue.main.async {
self.writeHandler?(error)
}
}
}
private func read(tag: NFCNDEFTag, session: NFCNDEFReaderSession) {
tag.readNDEF { [unowned self] message, error in
session.alertMessage = "読み込み完了♬(ノ゜∇゜)ノ♩"
session.invalidate()
let text = message?.records.compactMap {
switch $0.typeNameFormat {
case .nfcWellKnown:
if let url = $0.wellKnownTypeURIPayload() {
return url.absoluteString
}
if let text = String(data: $0.payload, encoding: .utf8) {
return text
}
return nil
default:
return nil
}
}.joined(separator: "\n\n")
DispatchQueue.main.async {
self.readHandler?(text, error)
}
}
}
}
struct ContentView: View {
@State private var isAlertShown = false
@State private var alertMessage = ""
@State private var text = ""
@StateObject private var session = NFCSession()
var body: some View {
VStack(spacing: 16) {
TextField("何か入力", text: $text)
Button {
session.startReadSession { text, error in
if let error = error {
alertMessage = error.localizedDescription
} else {
alertMessage = text ?? "空だよ"
}
isAlertShown = true
}
} label: {
Text("Read")
}
Button {
session.startWriteSession(text: text) { error in
if let error = error {
alertMessage = error.localizedDescription
isAlertShown = true
}
}
} label: {
Text("Write")
}
}
.padding(16)
.alert(isPresented: $isAlertShown) {
Alert(
title: Text(""),
message: Text(alertMessage),
dismissButton: .default(Text("OK")))
}
}
}
環境と事前準備
環境と事前準備は Storyboard のとこ以外は前回と同じです。
実装
ほとんど実装は UIKit 版と同じですがポイントは NFCSession
です。
下記2つのクロージャを持たせて NFCNDEFReaderSessionDelegate
で値を受けたあとの処理を実行しています(たぶんメモリリークはしてないはず)。
private var writeHandler: ((Error?) -> Void)?
private var readHandler: ((String?, Error?) -> Void)?
今回もエラーハンドリングをあまりしていないのでアプリ作成時は適宜実装してください🙇♂️
おわりに
これで SwiftUI でも Core NFC でテキストと URL の読み書きができるようになりました🙌
読み書きできてもあまり用途が思いつかないですがとりあえずワンタップで任意の URL を書き込めるアプリを作ってみました。
NFC タグを持ち歩いていればどこかで使えるかも?
Discussion