Swift开发 - NFC

一、简介

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设备或者标签之间进行信息交换的数据格式。

  1. NDEF格式由各种 NDEF Messages 和 NDEF Records 组成。
  2. NDEF是轻量级的紧凑的二进制格式,可带有URL、vCard和NFC定义的各种数据类型。
  3. 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 功能:

  1. 飞行模式下
  2. 有一个 NFC 进程正在运行
  3. Apple 钱包正在使用
  4. 相机正在使用
  5. 重启手机后,没有解锁过
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值