NFC

Core NFC框架详细解析 (二) —— CoreNFC使用简

2020-06-07  本文已影响0人  刀客传奇
版本号 时间
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)进行构建,它可以使没有电源的设备存储小块数据,同时还使其他有源设备可以读取该数据。

iOSwatchOS设备已经内置了NFC硬件已有几年了。实际上,Apple Pay使用此技术与商店中的支付终端进行交互。但是,开发人员要到iOS 11才能使用NFC硬件。

苹果通过引入Core NFC提升了iOS 13中的NFC游戏。借助这项新技术,您可以对iOS设备进行编程,使其以新方式与周围的互联世界互动。本教程将向您展示一些使用该技术的方法。在此过程中,您将学习如何:

重要说明:要执行本教程中的所有步骤,您需要满足以下条件:

  • 物理iOS设备
  • 苹果开发者帐户
  • 您可以读取和写入的NFC硬件。许多在线零售商都以合理的价格携带NFC tag。通常,您可以以大约10美元的价格获得一包NFC标签。在说明中寻找表明它是可编程的或列出其存储容量(通常为300500字节)的内容。具有该近似容量的任何设备都超出了本教程的范围。

starter文件夹中打开starter项目。 使用项目应用程序,您将学习如何:

构建并运行。 您会看到以下内容:


Writing to Your First Tag

首先,在Project navigator中选择NeatoCache项目。 然后,转到Signing & Capability,然后选择+ Capability。 从列表中选择Near Field Communication Tag Reading

这将确保您的应用程序的配置文件(provisioning profile)设置为使用NFC

接下来,打开您的Info.plist并添加以下条目:

您需要此条目来向用户传达您正在使用NFC功能的用途,并符合Apple关于在应用程序中使用NFC的要求。

接下来,您将添加一个函数,该函数可以执行您的应用将处理的各种NFC任务。 打开NFCUtility.swift并将以下导入和类型别名添加到文件顶部:

import CoreNFC

typealias NFCReadingCompletion = (Result<NFCNDEFMessage?, Error>) -> Void
typealias LocationReadingCompletion = (Result<Location, Error>) -> Void

您需要导入CoreNFC才能使用NFC。 类型别名(type aliases)提供以下功能:

接下来,将以下属性和方法添加到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. Understanding NDEF

请注意,上面的代码引入了另一个首字母缩写词NDEF,代表NFC Data Exchange Format。这是用于写入或读取NFC设备的标准格式。您将使用两种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
    }
  }
}

您在上面的代码中正在做的事情是:

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)))
    }
  }
}

您在上面的代码中正在做的事情是:

5. Using NDEF Payload Types

NFCNDEFPayload支持几种不同类型的数据。在此示例中,您使用的是wellKnownTypeTextPayload(string:locale :)。这是一种非常简单的数据类型,它使用字符串和设备的当前语言环境。其他一些数据类型包含更复杂的信息。完整清单如下:

注意:本教程涵盖了Well-KnownUnknown。要了解其他类型,请查看本教程末尾列出的链接。

另请注意,类型可以具有子类型。例如,Well-known的具有TextURI的子类型。

您真的很接近!剩下的就是将用户界面连接到新代码。转到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()
  }
}

您对添加的内容应该有点熟悉,因为它与您写入标签的方式非常相似。

最后,打开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. 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])
}

您在上面的代码中正在做的事情是:

总体而言,这似乎与将字符串保存到标签中时没什么不同,只是增加了一个步骤,将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状态,然后:

与上面一样,构建并运行并设置位置。 如果愿意,可以使用之前的相同标签,因为新代码将覆盖以前保存的所有数据。

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使用简单示例,感兴趣的给个赞或者关注~~~

上一篇下一篇

猜你喜欢

热点阅读