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

版本号 时间
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) -> Void
typealias LocationReadingCompletion = (Result) -> 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使用简单示例,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(Core NFC框架详细解析 (三) —— CoreNFC使用简单示例(二))