文章目录
一、简介
1. NFC(近场通信)
NFC(Near Field Communication,NFC)近场通信,是一种短距高频的无线电技术,在13.56MHz频率运行于10厘米距离内。其传输速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三种。
官方文档地址:Core NFC
支持NFC的设备:
1、iOS11上的手机只支持读的功能,可以通过手机的NFC功能读标签.
2、iOS13以上的系统支持写的功能。
3、支持NFC的最低设备为iPhone 7和iPhone 7Plus
官方Demo地址:Building an NFC Tag-Reader App
使用了NFC技术的设备(比如手机)可以在彼此靠近的情况下进行数据交换,是由非接触式射频识别(RFID)及互连互通技术整合演变而来,通过在单一芯片上集成感应式读卡器、感应式卡片和点对点通信的功能,利用移动终端实现移动支付、电子票务、门禁、移动身份识别、防伪等应用。目前,苹果的CoreNFC对NFC的格式支持有限,暂时仅支持NDEF格式。
2.NDEF简介
NDEF(NFC Data Exchange Format)是一种能够在NFC设备或者标签之间进行信息交换的数据格式。
- NDEF格式由各种 NDEF Messages 和 NDEF Records 组成。
- NDEF是轻量级的紧凑的二进制格式,可带有URL、vCard和NFC定义的各种数据类型。
- NDEF的由各种数据记录组成,而各个记录由报头(Header)和有效载荷(Payload)组成,其中NDEF记录的数据类型和大小由记录载荷的报头注明,这里的报头包含3部分,分别为Length、Type和Identifier。
参考文档:NDEF消息格式
二、读取NFC标签NDEF
1. 导入头文件
import CoreNFC
2. 读取NDEF格式的NFC
2.1 本地配置
Adding Support for Background Tag Reading
2.1.1 打开读取NFC标签权限
同时生成.entitlements文件
2.1.2 添加权限描述
Privacy - NFC Scan Usage Description
2.1.3 代码
class NFCManager: NSObject {
enum NFCSupportsStatus {
case yes // 支持
case deviceNo // 硬件不支持
case systemNo // 系统不支持
}
typealias NFCScanSuccessBlock = (_ message: NFCNDEFMessage) -> Void
typealias NFCScanErrorBlock = (_ error: NSError) -> Void
// 单行单例
static let shared = NFCManager()
private override init() { }
public var scanSuccessBlock: NFCScanSuccessBlock?
public var scanErrorBlock: NFCScanErrorBlock?
// MARK: - private
private var isReading: Bool = false
private var session: NFCNDEFReaderSession?
/// 判断是否支持读写功能
public static func isSupportsNFCReading() -> NFCSupportsStatus {
if #available(iOS 11.0, *) {
if NFCNDEFReaderSession.readingAvailable == true {
return .yes
} else {
// 机型不支持NFC功能
return .deviceNo
}
} else {
// 当前系统不支持NFC功能
return .systemNo
}
}
/// 扫描NFC
public func scanNFCTag(successBlock: @escaping NFCScanSuccessBlock, errorBlock: @escaping NFCScanErrorBlock) {
scanSuccessBlock = successBlock
scanErrorBlock = errorBlock
isReading = true
beginScan()
}
// MARK: - private func
private func beginScan() {
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
session?.alertMessage = "把你的手机靠近卡片"
session?.begin()
}
// 停止扫描
private func invalidateSession() {
session?.invalidate()
}
}
extension NFCManager: NFCNDEFReaderSessionDelegate {
// 读取回调
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
if error.code == 200 {
// 取消扫描
}
if error.code == 201 {
// 扫描超时
}
}
// 读取成功回调iOS11-iOS12
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
if isReading == true {
messages.forEach { message in
session.alertMessage = "读取成功"
invalidateSession()
if let block = scanSuccessBlock {
block(message)
}
}
} else {
//ios11-ios12下没有写入功能返回失败
session.alertMessage = "读取失败"
invalidateSession()
}
}
// 读取成功回调iOS13
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
if tags.count > 1 {
session.alertMessage = "存在多个标签"
session.restartPolling()
return
}
guard let tag = tags.first else { return }
session.connect(to: tag) { error in
guard error == nil else {
session.alertMessage = "连接NFC标签失败"
self.invalidateSession()
return
}
tag.queryNDEFStatus { status, capacity, error in
guard error == nil else {
session.alertMessage = "查询NFC标签状态失败"
self.invalidateSession()
return
}
if status == .notSupported {
session.alertMessage = "标签不是NDEF格式"
self.invalidateSession()
return
}
if self.isReading == true {
// 读
tag.readNDEF { message, error in
guard error == nil else {
session.alertMessage = "读取NFC标签失败"
self.invalidateSession()
return
}
guard let message = message else {
session.alertMessage = "NFC标签为空"
self.invalidateSession()
return
}
session.alertMessage = "读取成功"
self.invalidateSession()
if let block = self.scanSuccessBlock {
block(message)
}
}
} else {
//ios11-ios12下没有写入功能返回失败
session.alertMessage = "读取失败"
self.invalidateSession()
}
}
}
}
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
}
}
3. 读取带标签的NFC
Creating NFC Tags from Your iPhone
3.1 需要在info.plist中添加对应的标签,标签为数组,添加所适配NFC的Aid
注意:swift项目中,如果不使用某标签,info.plist就不要添加,初始化的时候也不要带该标签,如果info.plist添加了该标签,一定要加Aid,否则会导致无法唤起NFC扫描的页面。
3.2 代码
class NFCManager: NSObject {
enum NFCSupportsStatus {
case yes // 支持
case deviceNo // 硬件不支持
case systemNo // 系统不支持
}
typealias NFCScanSuccessBlock = (_ message: NFCNDEFMessage) -> Void
typealias NFCScanErrorBlock = (_ error: String?) -> Void
// 单行单例
static let shared = NFCManager()
private override init() { }
public var scanSuccessBlock: NFCScanSuccessBlock?
public var scanErrorBlock: NFCScanErrorBlock?
// MARK: - private
private var isReading: Bool = false
private var tagSession: NFCTagReaderSession?
private var currentTag: NFCISO7816Tag?
/// 判断是否支持读写功能
public static func isSupportsNFCReading() -> NFCSupportsStatus {
if #available(iOS 11.0, *) {
if NFCNDEFReaderSession.readingAvailable == true {
return .yes
} else {
// 机型不支持NFC功能
return .deviceNo
}
} else {
// 当前系统不支持NFC功能
return .systemNo
}
}
/// 判断是否支持读写功能
public static func isSupportsNFCWrite() -> NFCSupportsStatus {
if #available(iOS 13.0, *) {
if NFCNDEFReaderSession.readingAvailable == true {
return .yes
} else {
// 机型不支持NFC功能
return .deviceNo
}
} else {
// 当前系统不支持NFC功能
return .systemNo
}
}
/// 扫描NFC
public func scanNFCTag(successBlock: @escaping NFCScanSuccessBlock, errorBlock: @escaping NFCScanErrorBlock) {
scanSuccessBlock = successBlock
scanErrorBlock = errorBlock
isReading = true
beginScan()
}
// MARK: - private func
private func beginScan() {
tagSession = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092], delegate: self)
tagSession?.alertMessage = "把你的手机靠近卡片"
tagSession?.begin()
}
// 停止扫描
private func invalidateSession() {
tagSession?.invalidate()
}
}
extension NFCManager: NFCTagReaderSessionDelegate {
// 开始扫描
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
if let block = scanErrorBlock {
block("扫描失败")
}
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
if tags.count > 1 {
session.alertMessage = "存在多个标签"
if let block = scanErrorBlock {
block("读卡错误")
}
return
}
guard let tag7816 = tags.first else { return }
switch tag7816 {
case .feliCa(_):
print("NFCFelicatag")
case let .iso7816(tag):
// 这里获取到的AID就是第一步中在info.plist中设置的ID (D2760000850101)这个值一般是卡商提供的,代表卡的应用表示。
tagSession?.connect(to: tag7816, completionHandler: {[weak self] error in
guard let self = self else { return }
guard error == nil else {
session.alertMessage = "查询NFC标签状态失败"
self.invalidateSession()
self.scanErrorBlock?("连接失败")
return
}
self.currentTag = tag
self.tagSession?.alertMessage = "已经识别到NFC"
self.invalidateSession()
})
case .iso15693(_):
print("NFCISO15693Tag")
case .miFare(_):
print("NFCMiFareTag")
default:
session.invalidate(errorMessage: "Tag not valid.")
return
}
}
}
4. 写数据
4.1 代码
// 写
func sendApduSingle(apduStr: String, comPlete: WriteBlock){
let sendData: Data = self.convertHexStrToData(hexStr: apduStr)
let cmd: NFCISO7816APDU = NFCISO7816APDU.init(data: sendData)!
var resultError: Error?
var result: Data?
self.currentTag?.sendCommand(apdu: cmd, completionHandler: { [weak self] (resultData, sw1, sw2, error) in
print("resultData:\(resultData)\nsw1:\(sw1)\nsw2:\(sw2)\nerror:\(String(describing: error))")
print("rusultData转16进制:\(String(describing: self?.string(from: resultData)))")
result = resultData
resultError = error
})
if resultError == nil{
comPlete(result ?? Data.init(),true)
}else{
comPlete(Data.init(),false)
}
}
//将十六进制字符串转化为 Data
func convertHexStrToData(hexStr:String) -> Data {
let bytes = self.bytes(from: hexStr)
// let bytes = hexStr.bytes(from: hexStr)
return Data.init(_:bytes)
}
// 将16进制字符串转化为 [UInt8]
// 使用的时候直接初始化出 Data
// Data(bytes: Array<UInt8>)
func bytes(from hexStr: String) -> [UInt8] {
assert(hexStr.count % 2 == 0, "输入字符串格式不对,8位代表一个字符")
var bytes = [UInt8]()
var sum = 0
// 整形的 utf8 编码范围
let intRange = 48...57
// 小写 a~f 的 utf8 的编码范围
let lowercaseRange = 97...102
// 大写 A~F 的 utf8 的编码范围
let uppercasedRange = 65...70
for (index, c) in hexStr.utf8CString.enumerated() {
var intC = Int(c.byteSwapped)
if intC == 0 {
break
} else if intRange.contains(intC) {
intC -= 48
} else if lowercaseRange.contains(intC) {
intC -= 87
} else if uppercasedRange.contains(intC) {
intC -= 55
} else {
assertionFailure("输入字符串格式不对,每个字符都需要在0~9,a~f,A~F内")
}
sum = sum * 16 + intC
// 每两个十六进制字母代表8位,即一个字节
if index % 2 != 0 {
bytes.append(UInt8(sum))
sum = 0
}
}
return bytes
}
//讲Data转String
func string(from data: Data) -> String {
return String(format: "%@", data as CVarArg)
}
三、使用问题
1. 无法使用后台 NFC 功能:
- 飞行模式下
- 有一个 NFC 进程正在运行
- Apple 钱包正在使用
- 相机正在使用
- 重启手机后,没有解锁过