Core NFC框架详细解析 (二) —— CoreNFC使用简
版本号 | 时间 |
---|---|
V1.0 | 2020.06.07 星期日 |
前言
今天翻阅苹果的API文档,发现多了一个框架Core NFC,看了下才看见是iOS11.0新添加的框架,这里我们就一起来看一下框架Core NFC。感兴趣的看下面几篇文章。
1. Core NFC框架详细解析 (一) —— 基本概览(一)
开始
首先看下主要内容:
在本教程中,您将学习如何使用CoreNFC无线连接到其他设备或
NFC
标签。内容来自翻译。
下面看下写作环境:
Swift 5, iOS 13, Xcode 11
Near Field Communication(NFC)
是一种用于短距离无线设备与其他设备共享数据或触发这些设备上的动作的技术。它使用射频场(radio frequency field)
进行构建,它可以使没有电源的设备存储小块数据,同时还使其他有源设备可以读取该数据。
iOS
和watchOS
设备已经内置了NFC
硬件已有几年了。实际上,Apple Pay
使用此技术与商店中的支付终端进行交互。但是,开发人员要到iOS 11
才能使用NFC硬件。
苹果通过引入Core NFC
提升了iOS 13
中的NFC
游戏。借助这项新技术,您可以对iOS设备进行编程,使其以新方式与周围的互联世界互动。本教程将向您展示一些使用该技术的方法。在此过程中,您将学习如何:
- 将标准信息写入
NFC tag
标签。 - 阅读该信息。
- 将自定义信息保存到标签
tag
。 - 修改标签上已经找到的数据。
重要说明:要执行本教程中的所有步骤,您需要满足以下条件:
- 物理iOS设备
- 苹果开发者帐户
- 您可以读取和写入的NFC硬件。许多在线零售商都以合理的价格携带
NFC tag
。通常,您可以以大约10
美元的价格获得一包NFC
标签。在说明中寻找表明它是可编程的或列出其存储容量(通常为300
至500
字节)的内容。具有该近似容量的任何设备都超出了本教程的范围。
在starter
文件夹中打开starter
项目。 使用项目应用程序,您将学习如何:
- 将
NFC tag
设置为“location”
。 - 扫描
location tag
以查看其名称和访客日志。 - 将访问者
(visitor)
添加到位置标签(location tag
)。
构建并运行。 您会看到以下内容:
Writing to Your First Tag
首先,在Project navigator
中选择NeatoCache
项目。 然后,转到Signing & Capability
,然后选择+ Capability
。 从列表中选择Near Field Communication Tag Reading
。
这将确保您的应用程序的配置文件(provisioning profile)
设置为使用NFC
。
接下来,打开您的Info.plist
并添加以下条目:
-
Key:
Privacy – NFC Scan Usage Description
-
Value:使用
NFC
读取和写入数据
您需要此条目来向用户传达您正在使用NFC
功能的用途,并符合Apple
关于在应用程序中使用NFC的要求。
接下来,您将添加一个函数,该函数可以执行您的应用将处理的各种NFC
任务。 打开NFCUtility.swift
并将以下导入和类型别名添加到文件顶部:
import CoreNFC
typealias NFCReadingCompletion = (Result<NFCNDEFMessage?, Error>) -> Void
typealias LocationReadingCompletion = (Result<Location, Error>) -> Void
您需要导入CoreNFC
才能使用NFC
。 类型别名(type aliases)
提供以下功能:
- NFCReadingCompletion用于完成通用标签读取任务。
- LocationReadingCompletion,用于读取配置为位置的标签
接下来,将以下属性和方法添加到NFCUtility
:
// 1
private var session: NFCNDEFReaderSession?
private var completion: LocationReadingCompletion?
// 2
static func performAction(
_ action: NFCAction,
completion: LocationReadingCompletion? = nil
) {
// 3
guard NFCNDEFReaderSession.readingAvailable else {
completion?(.failure(NFCError.unavailable))
print("NFC is not available on this device")
return
}
shared.action = action
shared.completion = completion
// 4
shared.session = NFCNDEFReaderSession(
delegate: shared.self,
queue: nil,
invalidateAfterFirstRead: false)
// 5
shared.session?.alertMessage = action.alertMessage
// 6
shared.session?.begin()
}
如果您由于不符合NFCNDEFReaderSessionDelegate
而此时遇到编译错误,请不要担心,您将立即修复此问题。
这是您刚刚做的:
- 1) 您添加
session
和completion
属性以存储活动的NFCreading session
及其完成块(completion block)
。 - 2) 添加静态函数作为NFC读写任务的入口点。通常,您将使用单例样式访问此函数和
NFCUtility
。 - 3) 确保设备支持NFC读取。否则,请返回
error
的complete
。 - 4) 创建一个
NFCNDEFReaderSession
,它代表活动的阅读会话。您还可以设置代表以通知NFC阅读会话的各种事件。 - 5) 您可以在会话上设置
alertMessage
属性,以使其在NFC
模式下向用户显示该文本。 - 6) 开始阅读会话。调用时,模态将向用户呈现您在上一步中设置的所有指令。
1. Understanding NDEF
请注意,上面的代码引入了另一个首字母缩写词NDEF
,代表NFC Data Exchange Format
。这是用于写入或读取NFC设备的标准格式。您将使用两种NDEF:
-
NDEF Record:其中包含您的有效载荷
(payload)
值,例如字符串,URL或自定义数据。它还包含有关该有效负载值的信息,例如长度和类型。此信息是CoreNFC
中的NFCNDEFPayload
。 -
NDEF Message:这是保存
NDEF
记录的数据结构。NDEF
消息中可以有一个或多个NDEF记录。
2. Detecting Tags
现在,您已经设置了NFCReaderSession
,现在遵循NFCUtility
成为代理了,这样就可以通知您在读取会话期间发生的各种事件。
将以下代码添加到NFCUtility.swift
的底部:
// MARK: - NFC NDEF Reader Session Delegate
extension NFCUtility: NFCNDEFReaderSessionDelegate {
func readerSession(
_ session: NFCNDEFReaderSession,
didDetectNDEFs messages: [NFCNDEFMessage]
) {
// Not used
}
}
您将在一秒钟内向此扩展添加更多内容,但请注意,在本教程中,您将不会对readerSession(_:didDetectNDEFs :)
进行任何操作。 您仅在此处添加它,因为必须遵守委托协议。
与NFC
技术的互动越多,您越会发现在读写过程的各个阶段遇到错误的可能性。 将以下方法添加到新扩展中以捕获这些错误:
private func handleError(_ error: Error) {
session?.alertMessage = error.localizedDescription
session?.invalidate()
}
代码的第一行应该看起来很熟悉。 它将在NFC模式视图中向用户显示错误消息。 如果发生错误,您还将使会话无效以终止会话并允许用户再次与该应用进行交互。
接下来,将以下方法添加到扩展中以处理NFC读取会话中的错误:
func readerSession(
_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error
) {
if let error = error as? NFCReaderError,
error.code != .readerSessionInvalidationErrorFirstNDEFTagRead &&
error.code != .readerSessionInvalidationErrorUserCanceled {
completion?(.failure(NFCError.invalidated(message:
error.localizedDescription)))
}
self.session = nil
completion = nil
}
添加此委托方法将清除到目前为止您遇到的所有编译错误。
最后,将最后一种方法添加到您的扩展中,以处理可能的NFC标签检测:
func readerSession(
_ session: NFCNDEFReaderSession,
didDetect tags: [NFCNDEFTag]
) {
guard
let tag = tags.first,
tags.count == 1
else {
session.alertMessage = """
There are too many tags present. Remove all and then try again.
"""
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(500)) {
session.restartPolling()
}
return
}
}
在这里,您可以实现在会话检测到您扫描标签时将调用的方法。
通常,您希望用户只有一个标签离手机足够近,但是您应该考虑多个标签。 如果检测到此情况,则将停止扫描并alert
给用户。 显示该消息后,您将重新启动阅读会话,并让您的用户再试一次。
3. Handling the Tag
知道有一个标签后,您可能想对它做些事情。 在readerSession(_:didDetect :)
中的guard
声明之后添加以下代码:
// 1
session.connect(to: tag) { error in
if let error = error {
self.handleError(error)
return
}
// 2
tag.queryNDEFStatus { status, _, error in
if let error = error {
self.handleError(error)
return
}
// 3
switch (status, self.action) {
case (.notSupported, _):
session.alertMessage = "Unsupported tag."
session.invalidate()
case (.readOnly, _):
session.alertMessage = "Unable to write to tag."
session.invalidate()
case (.readWrite, .setupLocation(let locationName)):
self.createLocation(name: locationName, with: tag)
case (.readWrite, .readLocation):
return
default:
return
}
}
}
您在上面的代码中正在做的事情是:
- 1) 使用当前的
NCFNDEFReaderSession
连接到检测到的标签。您需要执行此步骤以执行对标签的任何读取或写入。连接后,它将调用其完成处理程序,并可能发生任何错误。 - 2) 在标签中查询其
NDEF
状态,以查看是否支持NFC
设备。就您的NeatoCache
应用而言,状态必须为readWrite
。 - 3) 切换状态和
NFC
操作,并根据其值确定应执行的操作。在这里,您尝试使用createLocation(name:with :)
将标签设置为具有位置名称,该名称尚不存在,因此会遇到编译错误。不用担心,您稍后会添加它。同样,readLocation
操作也尚未处理。
4. Creating the Payload
到目前为止,您已经在寻找标签,连接标签并查询其状态。要完成对标签的写入设置,请在NFCUtility.swift
的末尾添加以下代码块:
// MARK: - Utilities
extension NFCUtility {
func createLocation(name: String, with tag: NFCNDEFTag) {
// 1
guard let payload = NFCNDEFPayload
.wellKnownTypeTextPayload(string: name, locale: Locale.current)
else {
handleError(NFCError.invalidated(message: "Could not create payload"))
return
}
// 2
let message = NFCNDEFMessage(records: [payload])
// 3
tag.writeNDEF(message) { error in
if let error = error {
self.handleError(error)
return
}
self.session?.alertMessage = "Wrote location data."
self.session?.invalidate()
self.completion?(.success(Location(name: name)))
}
}
}
您在上面的代码中正在做的事情是:
- 1) 创建文本
NFCNDEFPayload
。如前所述,这类似于NDEF
记录。 - 2) 使用有效负载创建新的
NFCNDEFMessage
,以便可以将其保存到NFC
设备。 - 3) 最后,将消息写入标签。
5. Using NDEF Payload Types
NFCNDEFPayload
支持几种不同类型的数据。在此示例中,您使用的是wellKnownTypeTextPayload(string:locale :)
。这是一种非常简单的数据类型,它使用字符串和设备的当前语言环境。其他一些数据类型包含更复杂的信息。完整清单如下:
Empty
Well-Known
MIME media-type
Absolute URI
External
Unknown
Unchanged
Reserved
注意:本教程涵盖了
Well-Known
和Unknown
。要了解其他类型,请查看本教程末尾列出的链接。另请注意,类型可以具有子类型。例如,
Well-known
的具有Text
和URI
的子类型。
您真的很接近!剩下的就是将用户界面连接到新代码。转到AdminView.swift
并替换以下代码:
Button(action: {
}) {
Text("Save Location…")
}
.disabled(locationName.isEmpty)
使用
Button(action: {
NFCUtility.performAction(.setupLocation(locationName: self.locationName)) { _ in
self.locationName = ""
}
}) {
Text("Save Location…")
}
.disabled(locationName.isEmpty)
这将进行调用,以使用在text field
中找到的文本来设置您的位置。
构建并运行,切换到应用程序的Admin
选项卡,输入名称并选择Save Location…
。
您会看到以下内容:
注意:请记住,您需要使用物理设备并具有支持写入功能的
NFC
标签。
将手机放在NFC
标签上后,您会看到一条消息,说明您的位置已成功保存。
6. Reading the Tag
很好! 现在,您已经有了一个可以在标签中写入字符串的应用程序,您就可以为读取标签提供支持。 返回NFCUtility.swift
并在readerSession(_:didDetect :)
中找到以下代码。
case (.readWrite, .readLocation):
return
现在,替换它使用下面:
case (.readWrite, .readLocation):
self.readLocation(from: tag)
是时候实现该readLocation(from :)
方法了。 将以下内容添加到包含createLocation(name:with :)
的Utilities
扩展中:
func readLocation(from tag: NFCNDEFTag) {
// 1
tag.readNDEF { message, error in
if let error = error {
self.handleError(error)
return
}
// 2
guard
let message = message,
let location = Location(message: message)
else {
self.session?.alertMessage = "Could not read tag data."
self.session?.invalidate()
return
}
self.completion?(.success(location))
self.session?.alertMessage = "Read tag."
self.session?.invalidate()
}
}
您对添加的内容应该有点熟悉,因为它与您写入标签的方式非常相似。
- 1) 首先,您开始读取标签。 如果可以读取,它将返回找到的所有消息。
- 2) 接下来,如果有,尝试从消息数据中创建一个
Location
。 这使用了一个接受NFCNDEFMessage
并将其命名的自定义初始化程序。 如果您感到好奇,可以在LocationModel.swift
中找到该初始化程序。
最后,打开VisitorView.swift
,并在scanSection
中,替换以下代码:
Button(action: {
}) {
Text("Scan Location Tag…")
}
使用下面
Button(action: {
NFCUtility.performAction(.readLocation) { location in
self.locationModel = try? location.get()
}
}) {
Text("Scan Location Tag…")
}
您已经准备好从标签中读取数据。 构建并运行。
在Visitors
选项卡上,点击Scan Location Tag…
。 您会在用户界面中看到以下内容以及您的位置名称:
Writing Different Data Types
尽管在某些情况下写字符串可能会完美地工作,但您可能会发现想要将其他类型的数据写到标签中。
为此,请在Utilities
扩展中的NFCUtility.swift
中添加以下内容:
private func read(
tag: NFCNDEFTag,
alertMessage: String = "Tag Read",
readCompletion: NFCReadingCompletion? = nil
) {
tag.readNDEF { message, error in
if let error = error {
self.handleError(error)
return
}
// 1
if let readCompletion = readCompletion,
let message = message {
readCompletion(.success(message))
} else if
let message = message,
let record = message.records.first,
let location = try? JSONDecoder()
.decode(Location.self, from: record.payload) {
// 2
self.completion?(.success(location))
self.session?.alertMessage = alertMessage
self.session?.invalidate()
} else {
self.session?.alertMessage = "Could not decode tag data."
self.session?.invalidate()
}
}
}
从现在开始,这种读取标签的新方法将成为您大多数活动的切入点。 如您所见,它仍然像以前一样读取标签。 但是,一旦读取标签,它将执行以下两项操作之一:
- 1) 调用
completion handler
并将消息传递给它。 这对于将多个NFC
任务链接在一起非常有用。 - 2) 解码有效负载
(payload)
,以便您可以解析标签的记录。 您将稍后再讲到这一点。
1. Writing Custom Data Instead of Strings
此时,您已经准备好将应用程序从编写字符串到标签转换为将自定义数据写入标签。 将以下内容添加到Utilities
扩展中:
private func createLocation(_ location: Location, tag: NFCNDEFTag) {
read(tag: tag) { _ in
self.updateLocation(location, tag: tag)
}
}
这是用于创建带有位置标签的新函数。 您可以看到它使用新的read(tag:alsertMessage:readCompletion :)
启动该过程,并调用了一个新函数来更新标签上的位置,还调用了一个新的暂时实施updateLocation(_:tag :)
方法。
由于您要替换将位置信息写入标签的方式,因此请删除NFCUtility
扩展程序开头的createLocation(name:with :)
,因为不再需要它。 另外,从以下代码在readerSession(_:didDetect :)
中更新代码:
case (.readWrite, .setupLocation(let locationName)):
self.createLocation(name: locationName, with: tag)
到下面
case (.readWrite, .setupLocation(let locationName)):
self.createLocation(Location(name: locationName), tag: tag)
在createLocation(_:tag:)
后面添加这个方法:
private func updateLocation(
_ location: Location,
withVisitor visitor: Visitor? = nil,
tag: NFCNDEFTag
) {
// 1
var alertMessage = "Successfully setup location."
var tempLocation = location
// 2
let jsonEncoder = JSONEncoder()
guard let customData = try? jsonEncoder.encode(tempLocation) else {
self.handleError(NFCError.invalidated(message: "Bad data"))
return
}
// 3
let payload = NFCNDEFPayload(
format: .unknown,
type: Data(),
identifier: Data(),
payload: customData)
// 4
let message = NFCNDEFMessage(records: [payload])
}
您在上面的代码中正在做的事情是:
- 1) 创建默认
alert
消息和临时位置。 稍后您将回到这些内容。 - 2) 对传递给函数的
Location
结构进行编码。 这会将模型转换为原始数据(Data)
。 这很重要,因为这是将任何自定义类型写入NFC
标签的方式。 - 3) 创建可以处理数据的有效负载
(payload)
。 但是,您现在使用unknown
作为格式。 这样做时,必须将type
和identifier
设置为空数据Data
,而有效负载(payload)
参数将承载实际的解码模型。 - 4) 将有效负载添加到新创建的消息中。
总体而言,这似乎与将字符串保存到标签中时没什么不同,只是增加了一个步骤,将Swift数据类型转换为标签可理解的内容。
2. Checking Tag Capacity
要完成将数据写入标签,请在updateLocation(_:withVisitor:tag)
中添加下一个代码块:
tag.queryNDEFStatus { _, capacity, _ in
// 1
guard message.length <= capacity else {
self.handleError(NFCError.invalidPayloadSize)
return
}
// 2
tag.writeNDEF(message) { error in
if let error = error {
self.handleError(error)
return
}
if self.completion != nil {
self.read(tag: tag, alertMessage: alertMessage)
}
}
}
上面的闭包尝试查询当前NDEF
状态,然后:
- 1) 确保设备有足够的存储空间来存储位置。 请记住,与您可能熟悉的设备相比,NFC标签通常具有极其有限的存储容量。
- 2) 将消息写入标签。
与上面一样,构建并运行并设置位置。 如果愿意,可以使用之前的相同标签,因为新代码将覆盖以前保存的所有数据。
3. Reading Your Custom Data
此时,如果您尝试读取标签,将会收到错误消息。 保存的数据不再是众所周知的类型。 要解决此问题,请在readerSession(_:didDetect :)
中替换以下代码:
case (.readWrite, .readLocation):
self.readLocation(from: tag)
使用
case (.readWrite, .readLocation):
self.read(tag: tag)
构建并运行并扫描标签。 因为您在没有任何completion
块的情况下调用read(tag:alertMessage:readCompletion :)
,所以它将解码消息的第一条记录中找到的数据。
Modifying Content
此应用程序的最后一项要求是保存访问此位置的人员的日志。 您的应用具有UI中已经存在的未使用功能,该功能允许用户输入其名称并将其添加到标签中。 到目前为止,您所做的工作将使其余的设置变得微不足道。 您已经可以将数据读取和写入标签,因此对其进行修改应该很容易。
在创建tempLocation
之后,在NFCUtility.swift
中,将此代码添加到updateLocation(_:withVisitor:tag :)
:
if let visitor = visitor {
tempLocation.visitors.append(visitor)
alertMessage = "Successfully added visitor."
}
在上面的代码中,您检查是否提供了visitor
。 如果是这样,则将其添加到该位置的Visitors
数组中。
接下来,将以下方法添加到Utilities
扩展中:
private func addVisitor(_ visitor: Visitor, tag: NFCNDEFTag) {
read(tag: tag) { message in
guard
let message = try? message.get(),
let record = message.records.first,
let location = try? JSONDecoder()
.decode(Location.self, from: record.payload)
else {
return
}
self.updateLocation(location, withVisitor: visitor, tag: tag)
}
}
这个新方法将读取一个标签,从中获取消息,并尝试对标签上的Location
进行解码。
接下来,在readerSession(_:didDetect :)
中,向switch
语句添加新的case
:
case (.readWrite, .addVisitor(let visitorName)):
self.addVisitor(Visitor(name: visitorName), tag: tag)
如果用户明确想要添加visitor
,则将调用在上一步中添加的函数。
剩下的就是更新VisitorView.swift
。 在visitorSection
中,替换以下代码:
Button(action: {
}) {
Text("Add To Tag…")
}
.disabled(visitorName.isEmpty)
使用
Button(action: {
NFCUtility
.performAction(.addVisitor(visitorName: self.visitorName)) { location in
self.locationModel = try? location.get()
self.visitorName = ""
}
}) {
Text("Add To Tag…")
}
.disabled(visitorName.isEmpty)
Build
并运行,然后转到Visitors
选项卡。 输入您的姓名,然后选择Add To Tag…
。 扫描后,您将看到更新的位置以及在标记上找到的访问者列表。
现在,您应该熟悉Core NFC
的基础知识。 框架中还有很多事情没有在本教程中提到。 例如,您可以添加标签的背景阅读,这可以为用户提供一种无需打开应用即可与您的应用进行交互的方式。 如果您使用Apple的Shortcuts
应用程序来自动化您的智能家居设备,这对您来说应该很熟悉。 您可以在此处找到有关此操作的更多信息:Adding Support for Background Tag Reading。
要了解更多信息,请查看以下一些重要资源:
Apple's Core NFC Documentation是NFC规范中Apple支持的所有内容的首选资源。
NFC Forum Homepage是放置有关NFC
所需的所有一般信息及其定义的规范的地方。
后记
本篇主要讲述了
CoreNFC
使用简单示例,感兴趣的给个赞或者关注~~~