Core NFC框架详细解析 (三) —— CoreNFC使用简
2020-06-07 本文已影响0人
刀客传奇
版本号 | 时间 |
---|---|
V1.0 | 2020.06.07 星期日 |
前言
今天翻阅苹果的API文档,发现多了一个框架Core NFC,看了下才看见是iOS11.0新添加的框架,这里我们就一起来看一下框架Core NFC。感兴趣的看下面几篇文章。
1. Core NFC框架详细解析 (一) —— 基本概览(一)
2. Core NFC框架详细解析 (二) —— CoreNFC使用简单示例(一)
源码
1. Swift
首先看下工程组织结构
下面就是源码了
1. NFCUtility.swift
import Foundation
import CoreNFC
typealias NFCReadingCompletion = (Result<NFCNDEFMessage?, Error>) -> Void
typealias LocationReadingCompletion = (Result<Location, Error>) -> Void
enum NFCError: LocalizedError {
case unavailable
case invalidated(message: String)
case invalidPayloadSize
var errorDescription: String? {
switch self {
case .unavailable:
return "NFC Reader Not Available"
case let .invalidated(message):
return message
case .invalidPayloadSize:
return "NDEF payload size exceeds the tag limit"
}
}
}
class NFCUtility: NSObject {
enum NFCAction {
case readLocation
case setupLocation(locationName: String)
case addVisitor(visitorName: String)
var alertMessage: String {
switch self {
case .readLocation:
return "Place tag near iPhone to read the location."
case .setupLocation(let locationName):
return "Place tag near iPhone to setup \(locationName)"
case .addVisitor(let visitorName):
return "Place tag near iPhone to add \(visitorName)"
}
}
}
private static let shared = NFCUtility()
private var action: NFCAction = .readLocation
// 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()
}
}
// MARK: - NFC NDEF Reader Session Delegate
extension NFCUtility: NFCNDEFReaderSessionDelegate {
func readerSession(
_ session: NFCNDEFReaderSession,
didDetectNDEFs messages: [NFCNDEFMessage]
) {
// Not used
}
private func handleError(_ error: Error) {
session?.alertMessage = error.localizedDescription
session?.invalidate()
}
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
}
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
}
// 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(Location(name: locationName), tag: tag)
case (.readWrite, .readLocation):
self.read(tag: tag)
case (.readWrite, .addVisitor(let visitorName)):
self.addVisitor(Visitor(name: visitorName), tag: tag)
default:
return
}
}
}
}
}
// MARK: - Utilities
extension NFCUtility {
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()
}
}
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()
}
}
}
private func createLocation(_ location: Location, tag: NFCNDEFTag) {
read(tag: tag) { _ in
self.updateLocation(location, tag: tag)
}
}
private func updateLocation(
_ location: Location,
withVisitor visitor: Visitor? = nil,
tag: NFCNDEFTag
) {
// 1
var alertMessage = "Successfully setup location."
var tempLocation = location
if let visitor = visitor {
tempLocation.visitors.append(visitor)
alertMessage = "Successfully added visitor."
}
// 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])
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)
}
}
}
}
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)
}
}
}
2. LocationModel.swift
import CoreNFC
struct Location: Codable {
let name: String
var visitors: [Visitor]
init(name: String) {
self.name = name
visitors = []
}
init?(message: NFCNDEFMessage) {
guard
let locationRecord = message.records.first,
let locationName = locationRecord.wellKnownTypeTextPayload().0
else {
return nil
}
name = locationName
visitors = []
}
}
struct Visitor: Codable, Hashable {
let name: String
init(name: String) {
self.name = name
}
}
3. AppView.swift
import SwiftUI
struct AppView: View {
var body: some View {
TabView {
VisitorView()
.tabItem {
VStack {
Image(systemName: "magnifyingglass")
Text("Visitors")
}
}
AdminView()
.tabItem {
VStack {
Image(systemName: "lock")
Text("Admin")
}
}
}
.accentColor(Color("rw-green"))
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
Group {
AppView()
}
}
}
4. VisitorView.swift
import SwiftUI
struct VisitorView: View {
@State private var visitorName = ""
@State private var locationModel: Location?
// swiftlint:disable multiple_closures_with_trailing_closure multiline_arguments
private var visitorSection: some View {
Section(header: Text("Visitor Information")) {
TextField("Enter Your Name", text: $visitorName)
.textContentType(.name)
.autocapitalization(.words)
Button(action: {
NFCUtility.performAction(.addVisitor(visitorName: self.visitorName)) { location in
self.locationModel = try? location.get()
self.visitorName = ""
}
}) {
Text("Add To Tag…")
}
.disabled(visitorName.isEmpty)
}
}
private var scanSection: some View {
Section {
Button(action: {
NFCUtility.performAction(.readLocation) { location in
self.locationModel = try? location.get()
}
}) {
Text("Scan Location Tag…")
}
}
}
// swiftlint:enable multiple_closures_with_trailing_closure multiline_arguments
private var scannedSection: some View {
locationModel.map { location in
Section(
header: Text("Location: \(location.name)"),
footer: Text("Visitors: \(location.visitors.count)")) {
ForEach(location.visitors, id: \.self) { visitor in
Text(visitor.name)
}
}
}
}
var body: some View {
NavigationView {
Form {
visitorSection
scanSection
scannedSection
}
.navigationBarTitle("Visitors")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct VisitorView_Previews: PreviewProvider {
static var previews: some View {
VisitorView()
}
}
5. AdminView.swift
import SwiftUI
struct AdminView: View {
@State private var locationName = ""
var body: some View {
NavigationView {
Form {
Section(
header: Text("Location Setup"),
footer: Text("This will clear all visitor data from tag.")
.fontWeight(.bold)
) {
TextField("Enter Location Name", text: $locationName)
.autocapitalization(.words)
// swiftlint:disable multiple_closures_with_trailing_closure multiline_arguments
Button(action: {
NFCUtility.performAction(.setupLocation(locationName: self.locationName)) { _ in
self.locationName = ""
}
}) {
Text("Save Location…")
}
.disabled(locationName.isEmpty)
}
// swiftlint:enable multiple_closures_with_trailing_closure multiline_arguments
}
.navigationBarTitle("Administration")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct AdminView_Previews: PreviewProvider {
static var previews: some View {
AdminView()
}
}
后记
本篇主要讲述了
CoreNFC
使用简单示例,感兴趣的给个赞或者关注~~~