iOS13 发布以后,苹果开放了NFC 写入的权限,相比起原来只能读取的半残状况,现在可以做的事情就更多了,刚好最近完成了一个NFC 读写相关的小应用,中间踩了不少坑,在此记录一下。值得注意的是虽然现在苹果在iOS13以后提供了写入的接口,但是仍然有不少限制。
首先是硬件方面的限制,只有iPhone7 以后的设备(包括iPhone7)才具有写入的功能,如果你查看苹果官网的设备信息会看到iPhon7 以后搭载的是“支持读卡器模式的 NFC”,而之前的iPhone 则是 “NFC”,换言之,只有iPhone7以后的设备才具有读写功能,并且目前还不支持卡模拟,而iPhone7之前的设备只有绑定银行卡付款的功能,连读取 NFC标签都做不到,实在是非常的残念….
除了这些,苹果支持的 NFC 芯片类型也有限,官网原话:
Your app can read tags to give users more information about their physical environment and the real-world objects in it. Using Core NFC, you can read Near Field Communication (NFC) tags of types 1 through 5 that contain data in the NFC Data Exchange Format (NDEF). For example, your app might give users information about products they find in a store or exhibits they visit in a museum.
Your app can also write data to tags, and interact with protocol specific tag such as ISO 7816, ISO 15693, FeliCa™, and MIFARE® tags.
我开发时使用的芯片有两种,一种是NDEF
,还有种是 ISO 15693
,所以接下来的例子主要围绕这两种芯片进行。
附上一张iPhone NFC设备支持表:
读取与写入
首先我们开始创建工程:
-
编译器:Xcode11
-
macOS:10.15.1
创建完成后在Signing & Capabilities
里面点击“+”号添加Near Field Communication Tag Reading
添加后系统会自动帮你生成.entitlements
的环境文件,在这里其实有个大坑,创建完成后自带有NEDF
的标签,我做的应用还需要支持ISO 15693
,根据官方文档添加以后,文件是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string> //需支持ISO 15693, 自己添加
</array>
</dict>
</plist>
这个环境文件是必须的,否则 NFC不会读取任何标签,但是这个配置有坑,一直到自己交付上传应用商店以前没有任何问题,但是最后上传到苹果应用商店时会报错
1
ERROR ITMS-90778:"Invalid entitlement for core nfc framework....because 'NDEF is disallowed'"
emm…..非常懵逼,查询资料后知道解决方式是删除上面文件中的<string>NDEF</string>
字段,只保留<string>TAG</string>
,个人感觉应该是苹果后期把 NFC标签做了整合,或者是NDEF 和 TAG 有相互干扰的情况,所以会出现这个错误,在解决的时候还遇到了一个小坑,有人说在Signing & Capabilities
中删除Near Field Communication Tag Reading
标签然后再重新添加可以解决,本人也试过,但还是报这个错,最后发现原因是删除后再添加Near Field Communication Tag Reading
,系统会自动创建.entitlements
文件,但是这个文件可能不会自动帮你引入到工程中,而是存在于项目的根目录中,该文件创建后仍然包含<string>NDEF</string>
,所以上传会报同样的错误…
NDEF
NDEF 标签的读取与写入苹果官网有很详细的例子Demo,Demo源码:下载
由于自己做的项目需要自动识别 NDEF
标签与ISO 15693
标签,所以并没有使用官网的方法,如果只需要读写NDEF
的标签,查看官网的demo就足够了
ISO 15693
想要对NFC 标签进行操作,我们首先需要先建立一个 会话,点击按钮以后弹出 NFC交互界面
1
2
3
4
5
6
7
8
9
10
11
12
13
func callNFC(workMode: NFCWorkModel) {
//判断设备是否能扫描标签
guard NFCTagReaderSession.readingAvailable else {
SVProgressHUD.showError(withStatus: NSLocalizedString("scanError", comment: ""))
return
}
tagSession = NFCTagReaderSession(pollingOption: NFCTagReaderSession.PollingOption.iso15693,
delegate: self, // 实现NFCTagReaderSessionDelegate
queue: nil)
tagSession?.alertMessage = NSLocalizedString("scanTip", comment: "")
tagSession?.begin()
}
建立会话时要求实现NFCTagReaderSessionDelegate
代理方法,主要用到的方法有两个,会话消失时会调用该方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// nfc 会话取消,交互界面消失会走该方法
func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) {
// Check the invalidation reason from the returned error.
if let readerError = error as? NFCReaderError {
// Show an alert when the invalidation reason is not because of a
// successful read during a single-tag read session, or because the
// user canceled a multiple-tag read session from the UI or
// programmatically using the invalidate method call.
if readerError.code != .readerSessionInvalidationErrorFirstNDEFTagRead,
readerError.code != .readerSessionInvalidationErrorUserCanceled {
DispatchQueue.main.async {
SVProgressHUD.showError(withStatus: error.localizedDescription)
}
}
}
// To read new tags, a new session instance is required.
//释放资源
tagSession = nil
}
获取到 NFC标签信息会调用该方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
//判断标签个数是否为多个
if tags.count > 1 {
let retryInterval = DispatchTimeInterval.milliseconds(500)
session.invalidate(errorMessage: NSLocalizedString("manyTagTip", comment: ""))
DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval) {
session.restartPolling()
}
return
}
//监测是否为ISO 15693标签
guard case let NFCTag.iso15693(resultTag) = tags.first! else {
return
}
//通过会话与NFC 芯片建立连接
session.connect(to: tags.first!) { [weak self] (error: Error?) in
//建立连接错误
if error != nil {
DDLogError("error-->\(String(describing: error?.localizedDescription))")
session.invalidate(errorMessage: NSLocalizedString("connectError", comment: ""))
session.invalidate()
return
}
//先默认读取的芯片为NDEF 芯片,查询NDEF 芯片状态
resultTag.queryNDEFStatus { (ndefStatus: NFCNDEFStatus, _: Int, error: Error?) in
let retryInterval = DispatchTimeInterval.milliseconds(200)
if ndefStatus == .notSupported { //ndef状态不支持, 连接芯片为ISO 15693芯片
//业务逻辑
//读取数据
// self?.iso15693Tag(s1Tag: resultTag, session: session)
//或者写入数据
//DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval) {
// 写数据
//self?.writeISO5693Data(resultTag: resultTag, session: session)
//}
return
} else if error != nil {
session.invalidate(errorMessage: error!.localizedDescription)
session.invalidate()
return
}
// NDEF 标签业务逻辑
// 读数据
// self?.ndefTag(ndefTag: resultTag, session: session)
return
// 写数据
// DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval) {
// self?.writeNdefData(resultTag: resultTag,
// session: session,
// ndefStatus: ndefStatus) // ndef芯片
//}
}
}
}
读取数据方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
func ndefTag(ndefTag: NFCISO15693Tag, session: NFCTagReaderSession) {
// 获取UID
sendUID(resultTag: ndefTag, session: session)
ndefTag.readNDEF { message, _ in
guard let payload = message?.records.last else {
DDLogError("message:\(String(describing: message))")
session.invalidate(errorMessage: "读取错误")
session.invalidate()
return
}
let touple = payload.wellKnownTypeTextPayload()
if let ndefStr = touple.0 {
DDLogDebug("nedfstr--->\(ndefStr)")
//ndefStr为获取到的标签数据
//...业务逻辑
session.alertMessage = NSLocalizedString("readSuccess", comment: "")
session.invalidate()
return
}
session.invalidate(errorMessage: "解析错误")
session.invalidate()
}
}
func iso15693Tag(s1Tag: NFCISO15693Tag, session: NFCTagReaderSession) {
// 获取UID
sendUID(resultTag: s1Tag, session: session)
//发送读取命令,读取的block 地址为0xF8
s1Tag.readSingleBlock(requestFlags: [.highDataRate, .address],
blockNumber: 0xF8) { data, error in
if error != nil {
DDLogError("error==>\(String(describing: error?.localizedDescription))")
session.invalidate(errorMessage:"读取错误")
session.invalidate()
return
}
let dataStr = Util.bytesToStr(bytes: [UInt8](data))
DDLogDebug("dataStr--->\(String(describing: dataStr))")
//dataStr 为[UInt8] 解析的字符串
//....业务逻辑
session.alertMessage = NSLocalizedString("readSuccess", comment: "")
session.invalidate()
}
}
写入数据方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// ndef 芯片写入数据
private func writeNdefData(resultTag: NFCISO15693Tag, session: NFCTagReaderSession, ndefStatus: NFCNDEFStatus) {
// command:自己拼接的写入信息,NDEF芯片可以写入多种信息,详情见官网,该处仅写入字符串
let textPayload = NFCNDEFPayload.wellKnownTypeTextPayload(string: command,
locale: Locale(identifier: "en"))
let message = NFCNDEFMessage(records: [textPayload!])
if ndefStatus == .readOnly {
session.invalidate(errorMessage: "标签为只读状态")
} else if ndefStatus == .readWrite {
// When a tag is read-writable and has sufficient capacity,
// write an NDEF message to it.
resultTag.writeNDEF(message) { (error: Error?) in
if error != nil {
session.invalidate(errorMessage: "写入失败")
} else {
//写入成功
// 获取UID
self.sendUID(resultTag: resultTag, session: session)
session.alertMessage = NSLocalizedString("writeSuccess", comment: "")
session.invalidate()
}
}
} else {
session.invalidate(errorMessage:"标签状态错误")
}
}
private func writeISO5693Data(resultTag: NFCISO15693Tag, session: NFCTagReaderSession) {
//command:根据业务需要自己拼接的16进制命令数组
//blockNumber: 写入地址
let writeData = command.map { UInt8($0) }
resultTag.writeSingleBlock(requestFlags: [.highDataRate, .address],
blockNumber: 0xF8,
dataBlock: Data(writeData)) { error in
if error != nil {
DDLogError("error==>\(String(describing: error?.localizedDescription))")
session.invalidate(errorMessage: "写入失败")
session.invalidate()
return
}
//写入成功
// 获取UID
self.sendUID(resultTag: resultTag, session: session)
session.invalidate()
}
return
}
相关的工具函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private func sendUID(resultTag: NFCISO15693Tag, session _: NFCTagReaderSession) {
let tagUID: [UInt8] = [UInt8](resultTag.identifier)
let identifier = Util.bytesToStr(bytes: tagUID)
readUIDSubject.onNext(identifier)
}
static func bytesToStr(bytes: [UInt8]) -> String {
var hexStr = ""
for index in 0 ..< bytes.count {
var str = bytes[index].description
if str.count == 1 {
str = "0" + str
} else {
let low = Int(str)! % 16
let hight = Int(str)! / 16
str = hexIntToStr(hexInt: hight) + hexIntToStr(hexInt: low)
}
hexStr += str
}
return hexStr
}
至此,iOS13 NFC的读取与写入就可以告一段落了,由于本人只涉及了两种芯片的开发,在其它方面肯定有所遗漏,如有错误或者更好的实现方法,欢迎来信讨论。
by:在开发期间苹果的官方文档给了自己很大的帮助:传送门