在2017的WWDC
,苹果终于发布了众多开发者期待已久的系统级框架 CoreNFC
。可能你对于NFC
是什么并不是很了解,简而言之,NFC
(近场通信)就是当两台硬件设备相距4cm
以内时可以实现互相通信。NFC
在商业上的应用是把NFC
芯片集成到各类卡片中,极大的加强安全性。目前 CoreNFC
只支持一种格式:NFC Data Exchange Format
,简称NDEF
(常被用于平板电脑和智能手机中)。
提示:接下来的教程你需要
Xcode9 beta
,同时你也需要一部iPhone7
或者iPhone 7 Plus
运行iOS 11
系统来验证本教程中的一些新特性。Xcode9 beta
可以同时支持Swift 3.2
和Swift 4.0
,本教程的代码都将使用Swift 4.0
来编写。只是在项目中导入CoreNFC
是无法构建或者编译一个App
的,必须在物理设备上才可以运行。同时你还需要一个已经付费的开发者账号才可以。
什么是CoreNFC
通过
NFC
,你可以阅读到包括NFC数据交换格式
(NDEF)数据中的1到5类的近场通讯标签。读取标签,你的App创建了一个NFC数据交换格式
的reader session
并且提供了一个delegate
。正在运行的reader session
轮询NFC
标签,并在delegate
方法中找到包含NDEF
消息的标签时传递消息,并将该消息传递给delegate
。delegate
可以读取消息并处理异常。
NFC
谈不上“新”技术。苹果终于向开发者开放了这个 API
,以便我们可以利用 NFC
识别信息。
举一个实际的例子
你经营一家商店,希望客户进入后,使用有NFC
功能的手机扫描一件商品,就直接完成了所有的流程。没有任何麻烦,没有等待时间。作为一名App
开发人员,在不使用NFC
的情况下,还可以选择条形码
或二维码
,但依然比NFC
麻烦很多。
说来说去NFC
到底是什么?苹果虽然放开了NFC
的使用权限,但苹果公司严格限制了我们的访问。这意味着CoreNFC
只支持前面提到的NDEF
格式。如果您打算使用CoreNFC
替换您的`RFID卡,恐怕还有待时间。
准备工作
我们将用一个 demo
来展示如何使用 CoreNFC
。我们的应用程序将读取存储在 NDEF格式
卡上的信息。
为此,我使用 Arduino Uno
与 Adafruit PN532 Shield
配对,将消息编程到 NDEF
格式的样品卡上。如果您没有这些工具,或者根本不想将时间和金钱投入到这样的硬件中,请尝试找到一张带有消息的预格式化卡。在本教程中,我将不会将 NFC
格式化或将消息嵌入到 NDEF
卡中。
我们开始吧
要创建我们的项目,请打开 Xcode 9
并创建一个新的 single-view application
。然后命名您的项目,并确保选择 Swift
作为您的语言。
设计消息视图
为达到目的,我们需要制作用户界面(UI),供用户进行交互。我们先创建一个导航控制器
。点击 Main.storyboard
并选择View-Controller
。然后,转到status-Bar
,然后单击Editor
>embed in
>Navigation Controller
。这会在View-Controller
的顶部创建一个导航栏。您可以选择一个合适的标题。我起的标题为 Message in a Bottle
。
接下来,拖动UIButton
并将其放在 View-Controller
的底部。将按钮的文本更改为扫描
,并根据需要进行初始化。添加扫描按钮后,利用UILabel
添加背景标题。
现在我们的应用应该是这样的:
设置扫描和消息
现在我们已经做好了基础设置,接下来我们将为按钮
和标签
通过拖线设置属性,并为按钮
设置点击行为。
代码如下:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var messageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func scanPressed(_ sender: Any) {
// this is our newly created IBAction
}
}
设置应用程序权限和隐私
接下来,在我们真正开始研究我们的 NFC
实现之前,我们需要设置我们的应用权限。
注意:您必须非常仔细地关注此部分,否则您的应用配置将无法正常工作。此外,您将需要付费的开发者帐户。
App ID
首先,请转到 developer.apple.com
登录您的帐户,一旦您进入 account
面板,请转到 Certificates , Identifiers & Profiles
标签页。在Identifiers
下,单击App IDs
。然后点击 (+)
注册一个新的App ID
。App ID
说明应简单(例如NFC
)。
填入 Explicit App ID
和 Bundle ID
。 必须与您在 Xcode
项目中使用的Bundle ID
完全一致,就是com.YOURDOMAIN.Message-in-a-Bottle
。一旦你把你的Bundle ID
放入,向下滚动并检测服务列表。点击下一步,确保您的确认页面与我的相似:
Provisioning Profiles
完成设置后,我们需要为此应用程序创建一个新的配置文件
。转到 Provisioning Profiles
选项卡,然后单击 all
。然后,单击(+)
创建一个新的配置文件
。选择iOS Development
,继续选择 App ID
的名称(我的是NFC
),继续选择您使用的证书
,添加想要测试此应用程序的任何手机。命名新建的配置文件
,我们就生成了一个新的有效的配置文件
。
此处解释一下什么是
配置文件
?配置文件
要验证在所选设备上运行的特定的应用。这样,您可以确认在设备上运行的应用程序可追溯出处并确保安全。
所以我们也需要为我们的App
选择特定的配置文件
。为此,请返回Xcode
>build setting
> 禁用Automatically manage signing
。对于Debug
和Release
,选择下拉菜单并选择Download profile
。找到对应的相关配置文件,前期准备工作就完成了!
App Entitlements
然而,Xcode
团队尚未启用 CoreNFC
的自动授权。现在,下载此预构建的授权文件,并将文件位置放在 Project
> build setting
> Code Signing Entitlement
的文本框中。
我们预计苹果将在不久的将来更新此功能,但现在这个步骤不可避免。所以如果发布了一个新的测试版本,可以回到这个教程重新测试。
App Privacy
打开 Info.plist
并右键单击以添加一行。在 Key
列中,打开下拉菜单并选择 Privacy - NFC Scan Usage Description
。在 value
中设置自定义的提示信息。我们对 plist
文件的更新允许我们的 App
获取访问 NFC
的必要权限。
实现CoreNFC
接下来让我们来看看有趣的部分!我们将继续向 ViewController.swift
添加几行代码。但在我们写代码之前,需要提一下一个让我调试了几个小时的问题。
目前 CoreNFC
框架尚未编译进 iOS
模拟器。这意味着如果你尝试import CoreNFC
你会得到一个错误,说没有 CoreNFC
模块。简单的修复方法就是主动选择您的 iPhone 或 通用设备。
import UIKit
import CoreNFC
class ViewController: UIViewController, NFCNDEFReaderSessionDelegate {
@IBOutlet weak var messageLabel: UILabel!
var nfcSession: NFCNDEFReaderSession?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func scanPressed(_ sender: Any) {
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print("The session was invalidated: \(error.localizedDescription)")
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
// Parse the card's information
}
我们只需要再添加两行代码就可以启动 NFC reader
。更新我们之前创建的 scanPressed
方法来调用NFCNDEFReaderSession初始化器。
@IBAction func scanPressed(_ sender: Any) {
nfcSession = NFCNDEFReaderSession.init(delegate: self, queue: nil, invalidateAfterFirstRead: true)
nfcSession?.begin()
}
如果您的程序在运行时出现 Session is invalidated unexpectedly
错误,请返回并再次检查 设置权限和隐私
部分。
解析消息记录
首先,我们来看看 func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage])
我们可以通过打印messages
的第一个元素来认识它。
(
// Payload one (There's only one payload in this card)
"TNF=1, /* Type Name Format */
Payload Type=<55>,
Payload ID=<>,
Payload=<0048656c 6c6f21>" /* What we're really interested in */
)
`messages` 是一个 存储`NFCNDEFMessages` 数据格式数组,在 `NFC` 会话无效之前,我们执行的每次扫描都有一个数组,而在我们扫描后,会话会自动失效。我们只需要关注数组中的一个对象。
messages[0]
是一个 NFCNDEFMessage
,它包含一个 NFCNDEFPayload
。
messages[0].records
是一个 NFCNDEFPayload
数组,因为 NDEF卡
可以包含多个 payLoad
。
单一 NFCNDEFPayload
包含4项信息:
- identifier
- type
- typeNameFormat
- payload是一个
Data
类型的对象
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
var result = ""
for payload in messages[0].records {
result += String.init(data: payload.payload.advanced(by: 3), encoding: .utf8)! // 1
}
DispatchQueue.main.async {
self.messageLabel.text = result
}
此处有几个问题。为什么要前进3?DispatchQueue的内容是什么?
为什么 NFC
标签总是以 enHello
或 enMessage
开头。在对 NDEF
规范和法规进行了一些研究后发现:
所有语言代码必须根据
RFC 3066
完成。语言代码不能省略。语言代码长度被编码在状态字节的六个最低有效位中。因此,通过使用值0x3F
屏蔽状态字节很容易找到。
第3行将 payload
从 data
类型转换为可读的 string
字符串。”
关于这个问题 DispatchQueue
,在readerSession
中 messageLabel
是不可访问的。所以我们要返回主线程,给 messageLabel
赋值,这也是线程间通信的一个最简单的方法。
这是最终的样式: