Created
August 27, 2021 20:45
-
-
Save treastrain/bbe368a74f4094fbc105f1f51c90b6dd to your computer and use it in GitHub Desktop.
SwiftUI App + Core NFC + Swift Concurrency を使って書いた、交通系電子マネーカードの残高読み取りサンプル。 https://twitter.com/treastrain/status/1431356306963587072
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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