×
广告

iOS11中使用CoreNFC

96
Liberalism
2017.10.30 13:32* 字数 2324

在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.2Swift 4.0,本教程的代码都将使用Swift 4.0来编写。只是在项目中导入CoreNFC是无法构建或者编译一个App的,必须在物理设备上才可以运行。同时你还需要一个已经付费的开发者账号才可以。

什么是CoreNFC

通过NFC,你可以阅读到包括NFC数据交换格式(NDEF)数据中的1到5类的近场通讯标签。读取标签,你的App创建了一个NFC数据交换格式reader session并且提供了一个 delegate 。正在运行的reader session 轮询 NFC 标签,并在 delegate 方法中找到包含 NDEF 消息的标签时传递消息,并将该消息传递给 delegatedelegate可以读取消息并处理异常。

NFC谈不上“新”技术。苹果终于向开发者开放了这个 API,以便我们可以利用 NFC 识别信息。

举一个实际的例子
你经营一家商店,希望客户进入后,使用有NFC功能的手机扫描一件商品,就直接完成了所有的流程。没有任何麻烦,没有等待时间。作为一名App开发人员,在不使用NFC的情况下,还可以选择条形码二维码,但依然比NFC麻烦很多。

说来说去NFC到底是什么?苹果虽然放开了NFC的使用权限,但苹果公司严格限制了我们的访问。这意味着CoreNFC只支持前面提到的NDEF格式。如果您打算使用CoreNFC替换您的`RFID卡,恐怕还有待时间。

准备工作

我们将用一个 demo 来展示如何使用 CoreNFC 。我们的应用程序将读取存储在 NDEF格式 卡上的信息。

为此,我使用 Arduino UnoAdafruit PN532 Shield 配对,将消息编程到 N​​DEF 格式的样品卡上。如果您没有这些工具,或者根本不想将时间和金钱投入到这样的硬件中,请尝试找到一张带有消息的预格式化卡。在本教程中,我将不会将 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 IDApp ID说明应简单(例如NFC)。
填入 Explicit App IDBundle 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 。对于 DebugRelease,选择下拉菜单并选择 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项信息:

  1. identifier
  2. type
  3. typeNameFormat
  4. 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 标签总是以 enHelloenMessage 开头。在对 NDEF 规范和法规进行了一些研究后发现:

所有语言代码必须根据 RFC 3066 完成。语言代码不能省略。语言代码长度被编码在状态字节的六个最低有效位中。因此,通过使用值 0x3F 屏蔽状态字节很容易找到。

第3行将 payloaddata 类型转换为可读的 string 字符串。”

关于这个问题 DispatchQueue,在readerSessionmessageLabel 是不可访问的。所以我们要返回主线程,给 messageLabel 赋值,这也是线程间通信的一个最简单的方法。

这是最终的样式:

工作笔记
Web note ad 1