Skip to content

Instantly share code, notes, and snippets.

@treastrain
Created August 27, 2021 20:45
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save treastrain/bbe368a74f4094fbc105f1f51c90b6dd to your computer and use it in GitHub Desktop.
Save treastrain/bbe368a74f4094fbc105f1f51c90b6dd to your computer and use it in GitHub Desktop.
SwiftUI App + Core NFC + Swift Concurrency を使って書いた、交通系電子マネーカードの残高読み取りサンプル。 https://twitter.com/treastrain/status/1431356306963587072
//
// ConcurrencyNFCApp.swift
// ConcurrencyNFC
//
// Created by treastrain on 2021/08/28.
//
import SwiftUI
import CoreNFC
@main
struct ConcurrencyNFCApp: App {
@ObservedObject var viewModel = ViewModel()
@State private var balance: String = "¥----"
var body: some Scene {
WindowGroup {
NavigationView {
VStack {
Text(balance).font(.largeTitle).padding()
Spacer()
Button {
Task {
do {
let balance = try await viewModel.scan()
self.balance = balance.formatted(.currency(code: "JPY"))
} catch NFCReaderError.readerSessionInvalidationErrorUserCanceled {
// do nothing
} catch { self.balance = error.localizedDescription }
}
} label: { Text("Scan").font(.title) }
.padding()
}
.navigationTitle("Concurrency NFC")
}
}
}
}
class ViewModel: NSObject, ObservableObject {
enum NFCError: Error {
case readingUnavailable
}
private var activeContinuation: CheckedContinuation<Int, Error>?
private var session: NFCTagReaderSession?
func scan() async throws -> Int {
guard NFCTagReaderSession.readingAvailable else { throw NFCError.readingUnavailable }
return try await withCheckedThrowingContinuation { continuation in
activeContinuation = continuation
session = NFCTagReaderSession(pollingOption: .iso18092, delegate: self)
session?.alertMessage = "Place the top of your iPhone on the card."
session?.begin()
}
}
}
extension ViewModel: NFCTagReaderSessionDelegate {
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
activeContinuation?.resume(throwing: error)
activeContinuation = nil
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
Task { () -> Void in
try await session.connect(to: tags.first!)
guard case .some(.feliCa(let feliCaTag)) = session.connectedTag else {
return session.invalidate(errorMessage: "The tag is not FeliCa.")
}
session.alertMessage = "Scanning..."
let (status1, status2, blockData) = try await feliCaTag.readWithoutEncryption(serviceCodeList: [Data([0x8B, 0x00])], blockList: [Data([0x80, 0x00])])
guard status1 == 0, status2 == 0, let data = blockData.first else {
return session.invalidate(errorMessage: "Status flags indicate an error, or the block data is invalid.")
}
session.alertMessage = "Succeeded!"
session.invalidate()
activeContinuation?.resume(returning: data.toIntReversed(11, 12))
activeContinuation = nil
}
}
}
extension Data {
/// https://github.com/treastrain/TRETJapanNFCReader/blob/master/TRETJapanNFCReader/Extensions.swift#L112
func toIntReversed(_ startIndex: Int, _ endIndex: Int) -> Int {
var s = 0
for (n, i) in (startIndex...endIndex).enumerated() {
s += Int(self[i]) << (n * 8)
}
return s
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment