From 4cb24d1cdcd0106f0f28ed96071ada297ab84aa4 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Sun, 28 Jan 2024 23:44:27 +0900
Subject: [PATCH 01/17] Add Async module
---
Package.swift | 1 +
Sources/Async/_exported.swift | 11 +++++++++++
2 files changed, 12 insertions(+)
create mode 100644 Sources/Async/_exported.swift
diff --git a/Package.swift b/Package.swift
index 0d4fc60..680486b 100644
--- a/Package.swift
+++ b/Package.swift
@@ -50,6 +50,7 @@ let assertServices = add(moduleName: "AssertServices", includesTest: false)
let infoPListChecker = add(moduleName: "InfoPListChecker", includesTest: false)
// MARK: - Modules - Primary
+let async = add(moduleName: "Async", includesTest: false)
let core = add(moduleName: "Core", dependencies: [assertServices, infoPListChecker], includesTest: true)
let nativeTag = add(moduleName: "NativeTag", dependencies: [core], includesTest: true)
add(moduleName: "NDEFMessage", dependencies: [core], includesTest: true)
diff --git a/Sources/Async/_exported.swift b/Sources/Async/_exported.swift
new file mode 100644
index 0000000..aa2c51a
--- /dev/null
+++ b/Sources/Async/_exported.swift
@@ -0,0 +1,11 @@
+//
+// _exported.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+@_exported import Foundation
+#if canImport(CoreNFC)
+@_exported import CoreNFC
+#endif
From 7ae4017a5dfbe6b9577cd64667afcf792f3dccf0 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:15 +0900
Subject: [PATCH 02/17] Create AsyncNFCReaderSession.swift
---
Sources/Async/AsyncNFCReaderSession.swift | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 Sources/Async/AsyncNFCReaderSession.swift
diff --git a/Sources/Async/AsyncNFCReaderSession.swift b/Sources/Async/AsyncNFCReaderSession.swift
new file mode 100644
index 0000000..1f4e3cd
--- /dev/null
+++ b/Sources/Async/AsyncNFCReaderSession.swift
@@ -0,0 +1,22 @@
+//
+// AsyncNFCReaderSession.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+public protocol AsyncNFCReaderSession: AnyObject {
+ associatedtype Event
+ associatedtype EventStream
+ var eventStream: EventStream { get }
+ var isReady: Bool { get }
+ var alertMessage: String { get set }
+ func start()
+ func stop()
+}
+
+#if canImport(CoreNFC)
+extension AsyncNFCReaderSession {
+ public static var readingAvailable: Bool { NFCReaderSession.readingAvailable }
+}
+#endif
From 41cae25928f8021ae2a72d51e679c65c436f41b0 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:52 +0900
Subject: [PATCH 03/17] Create NFCReaderSessionBridgeable.swift
---
Sources/Async/NFCReaderSessionBridgeable.swift | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 Sources/Async/NFCReaderSessionBridgeable.swift
diff --git a/Sources/Async/NFCReaderSessionBridgeable.swift b/Sources/Async/NFCReaderSessionBridgeable.swift
new file mode 100644
index 0000000..60a0fc6
--- /dev/null
+++ b/Sources/Async/NFCReaderSessionBridgeable.swift
@@ -0,0 +1,16 @@
+//
+// NFCReaderSessionBridgeable.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+protocol NFCReaderSessionBridgeable: AnyObject {
+ associatedtype Error
+ var sessionDidBecomeActive: () -> Void { get set }
+ var sessionDidInvalidateWithError: (_ error: Error) -> Void { get set }
+ var isReady: Bool { get }
+ var alertMessage: String { get set }
+ func begin()
+ func invalidate()
+}
From fcbc818216878060f6427d3907f36620c5fd4dd8 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:19 +0900
Subject: [PATCH 04/17] Create AsyncNFCReaderSessionEvent.swift
---
Sources/Async/AsyncNFCReaderSessionEvent.swift | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 Sources/Async/AsyncNFCReaderSessionEvent.swift
diff --git a/Sources/Async/AsyncNFCReaderSessionEvent.swift b/Sources/Async/AsyncNFCReaderSessionEvent.swift
new file mode 100644
index 0000000..d138cd5
--- /dev/null
+++ b/Sources/Async/AsyncNFCReaderSessionEvent.swift
@@ -0,0 +1,14 @@
+//
+// AsyncNFCReaderSessionEvent.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+public protocol AsyncNFCReaderSessionEvent {
+ associatedtype Error
+ static var sessionIsReady: Self { get }
+ static var sessionStarted: Self { get }
+ static var sessionBecomeActive: Self { get }
+ static func sessionInvalidated(reason: Error) -> Self
+}
From 35805f42aed1f22fe449643978824be464eb4c67 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:38 +0900
Subject: [PATCH 05/17] Create AsyncNFCNDEFMessageReaderSession.swift
---
.../AsyncNFCNDEFMessageReaderSession.swift | 112 ++++++++++++++++++
1 file changed, 112 insertions(+)
create mode 100644 Sources/Async/AsyncNFCNDEFMessageReaderSession.swift
diff --git a/Sources/Async/AsyncNFCNDEFMessageReaderSession.swift b/Sources/Async/AsyncNFCNDEFMessageReaderSession.swift
new file mode 100644
index 0000000..45bd80b
--- /dev/null
+++ b/Sources/Async/AsyncNFCNDEFMessageReaderSession.swift
@@ -0,0 +1,112 @@
+//
+// AsyncNFCNDEFMessageReaderSession.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+#if canImport(CoreNFC)
+open class AsyncNFCNDEFMessageReaderSession: AsyncNFCReaderSession {
+ public init(
+ invalidateAfterFirstRead: Bool
+ ) {
+ (eventStream, eventStreamContinuation) = EventStream.makeStream()
+ bridge = .init(invalidateAfterFirstRead: invalidateAfterFirstRead)
+ bridge.sessionDidBecomeActive = { [unowned self] in
+ eventStreamContinuation.yield(.sessionBecomeActive)
+ }
+ bridge.sessionDidInvalidateWithError = { [unowned self] in
+ eventStreamContinuation.yield(.sessionInvalidated(reason: $0))
+ eventStreamContinuation.finish()
+ }
+ bridge.sessionDidDetectNDEFs = { [unowned self] in
+ eventStreamContinuation.yield(.sessionDetected(messages: $0))
+ }
+ eventStreamContinuation.yield(.sessionIsReady)
+ }
+
+ public let eventStream: EventStream
+
+ public var isReady: Bool {
+ bridge.isReady
+ }
+
+ public var alertMessage: String {
+ get { bridge.alertMessage }
+ set { bridge.alertMessage = newValue }
+ }
+
+ public func start() {
+ bridge.begin()
+ eventStreamContinuation.yield(.sessionStarted)
+ }
+
+ public func stop() {
+ bridge.invalidate()
+ }
+
+ private let bridge: NFCNDEFMessageReaderSessionBridge
+ private let eventStreamContinuation: EventStream.Continuation
+}
+
+extension AsyncNFCNDEFMessageReaderSession {
+ public enum Event: AsyncNFCReaderSessionEvent {
+ case sessionIsReady
+ case sessionStarted
+ case sessionBecomeActive
+ case sessionDetected(messages: [NFCNDEFMessage])
+ case sessionInvalidated(reason: NFCReaderError)
+ }
+}
+
+extension AsyncNFCNDEFMessageReaderSession {
+ public typealias EventStream = AsyncStream
+}
+
+private final class NFCNDEFMessageReaderSessionBridge: NSObject, NFCReaderSessionBridgeable, NFCNDEFReaderSessionDelegate {
+ private lazy var session: NFCNDEFReaderSession = { preconditionFailure("`session` has not been set.") }()
+ lazy var sessionDidBecomeActive: () -> Void = { preconditionFailure("`sessionDidBecomeActive` has not been set.") }()
+ lazy var sessionDidInvalidateWithError: (_ error: NFCReaderError) -> Void = { preconditionFailure("`sessionDidInvalidateWithError` has not been set.") }()
+ lazy var sessionDidDetectNDEFs: (_ messages: [NFCNDEFMessage]) -> Void = { preconditionFailure("`sessionDidDetectNDEFs` has not been set.") }()
+
+ init(
+ invalidateAfterFirstRead: Bool
+ ) {
+ super.init()
+ session = .init(
+ delegate: self,
+ queue: nil,
+ invalidateAfterFirstRead: invalidateAfterFirstRead
+ )
+ }
+
+ var isReady: Bool {
+ session.isReady
+ }
+
+ var alertMessage: String {
+ get { session.alertMessage }
+ set { session.alertMessage = newValue }
+ }
+
+ func begin() {
+ session.begin()
+ }
+
+ func invalidate() {
+ session.invalidate()
+ }
+
+ func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
+ sessionDidBecomeActive()
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: any Error) {
+ sessionDidInvalidateWithError(error as! NFCReaderError)
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
+ sessionDidDetectNDEFs(messages)
+ }
+}
+#endif
From da351cef35b8366109bd10f5635325e69f05695a Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:42 +0900
Subject: [PATCH 06/17] Create AsyncNFCNDEFTagReaderSession.swift
---
.../Async/AsyncNFCNDEFTagReaderSession.swift | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
create mode 100644 Sources/Async/AsyncNFCNDEFTagReaderSession.swift
diff --git a/Sources/Async/AsyncNFCNDEFTagReaderSession.swift b/Sources/Async/AsyncNFCNDEFTagReaderSession.swift
new file mode 100644
index 0000000..38d06e3
--- /dev/null
+++ b/Sources/Async/AsyncNFCNDEFTagReaderSession.swift
@@ -0,0 +1,132 @@
+//
+// AsyncNFCNDEFTagReaderSession.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+#if canImport(CoreNFC)
+open class AsyncNFCNDEFTagReaderSession: AsyncNFCReaderSession {
+ public init(
+ invalidateAfterFirstRead: Bool
+ ) {
+ (eventStream, eventStreamContinuation) = EventStream.makeStream()
+ bridge = .init(invalidateAfterFirstRead: invalidateAfterFirstRead)
+ bridge.sessionDidBecomeActive = { [unowned self] in
+ eventStreamContinuation.yield(.sessionBecomeActive)
+ }
+ bridge.sessionDidInvalidateWithError = { [unowned self] in
+ eventStreamContinuation.yield(.sessionInvalidated(reason: $0))
+ eventStreamContinuation.finish()
+ }
+ bridge.sessionDidDetect = { [unowned self] in
+ eventStreamContinuation.yield(.sessionDetected(tags: $0))
+ }
+ eventStreamContinuation.yield(.sessionIsReady)
+ }
+
+ public let eventStream: EventStream
+
+ public var isReady: Bool {
+ bridge.isReady
+ }
+
+ public var alertMessage: String {
+ get { bridge.alertMessage }
+ set { bridge.alertMessage = newValue }
+ }
+
+ public func start() {
+ bridge.begin()
+ eventStreamContinuation.yield(.sessionStarted)
+ }
+
+ public func connect(to tag: any NFCNDEFTag) async throws {
+ try await bridge.connect(to: tag)
+ }
+
+ public func stop() {
+ bridge.invalidate()
+ }
+
+ public func stop(errorMessage: String) {
+ bridge.invalidate(errorMessage: errorMessage)
+ }
+
+ private let bridge: NFCNDEFTagReaderSessionBridge
+ private let eventStreamContinuation: EventStream.Continuation
+}
+
+extension AsyncNFCNDEFTagReaderSession {
+ public enum Event: AsyncNFCReaderSessionEvent {
+ case sessionIsReady
+ case sessionStarted
+ case sessionBecomeActive
+ case sessionDetected(tags: [any NFCNDEFTag])
+ case sessionInvalidated(reason: NFCReaderError)
+ }
+}
+
+extension AsyncNFCNDEFTagReaderSession {
+ public typealias EventStream = AsyncStream
+}
+
+private final class NFCNDEFTagReaderSessionBridge: NSObject, NFCReaderSessionBridgeable, NFCNDEFReaderSessionDelegate {
+ private lazy var session: NFCNDEFReaderSession = { preconditionFailure("`session` has not been set.") }()
+ lazy var sessionDidBecomeActive: () -> Void = { preconditionFailure("`sessionDidBecomeActive` has not been set.") }()
+ lazy var sessionDidInvalidateWithError: (_ error: NFCReaderError) -> Void = { preconditionFailure("`sessionDidInvalidateWithError` has not been set.") }()
+ lazy var sessionDidDetect: (_ tags: [any NFCNDEFTag]) -> Void = { preconditionFailure("`sessionDidDetect` has not been set.") }()
+
+ init(
+ invalidateAfterFirstRead: Bool
+ ) {
+ super.init()
+ session = .init(
+ delegate: self,
+ queue: nil,
+ invalidateAfterFirstRead: invalidateAfterFirstRead
+ )
+ }
+
+ var isReady: Bool {
+ session.isReady
+ }
+
+ var alertMessage: String {
+ get { session.alertMessage }
+ set { session.alertMessage = newValue }
+ }
+
+ func begin() {
+ session.begin()
+ }
+
+ func connect(to tag: any NFCNDEFTag) async throws {
+ try await session.connect(to: tag)
+ }
+
+ func invalidate() {
+ session.invalidate()
+ }
+
+ func invalidate(errorMessage: String) {
+ session.invalidate(errorMessage: errorMessage)
+ }
+
+ func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
+ sessionDidBecomeActive()
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: any Error) {
+ sessionDidInvalidateWithError(error as! NFCReaderError)
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
+ fatalError("The reader session doesn't call this method when the bridge provides the readerSession(_:didDetect:) method.")
+ }
+
+ func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [any NFCNDEFTag]) {
+ sessionDidDetect(tags)
+ }
+}
+#endif
From 38b46fc874c09a7e99ada0a3bbd630fb5c4fad89 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:47 +0900
Subject: [PATCH 07/17] Create AsyncNFCTagReaderSession.swift
---
Sources/Async/AsyncNFCTagReaderSession.swift | 173 +++++++++++++++++++
1 file changed, 173 insertions(+)
create mode 100644 Sources/Async/AsyncNFCTagReaderSession.swift
diff --git a/Sources/Async/AsyncNFCTagReaderSession.swift b/Sources/Async/AsyncNFCTagReaderSession.swift
new file mode 100644
index 0000000..cbd5ac2
--- /dev/null
+++ b/Sources/Async/AsyncNFCTagReaderSession.swift
@@ -0,0 +1,173 @@
+//
+// AsyncNFCTagReaderSession.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+#if canImport(CoreNFC)
+open class AsyncNFCTagReaderSession: AsyncNFCReaderSession {
+ public init(
+ pollingOption: NFCTagReaderSession.PollingOption
+ ) {
+ (eventStream, eventStreamContinuation) = EventStream.makeStream()
+ bridge = .init(pollingOption: pollingOption)
+ guard let bridge else {
+ eventStreamContinuation.yield(
+ .sessionCreationFailed(
+ reason: pollingOption.isEmpty ? .pollingOptionIsEmpty : .systemNotAvailable
+ )
+ )
+ eventStreamContinuation.finish()
+ return
+ }
+ bridge.sessionDidBecomeActive = { [unowned self] in
+ eventStreamContinuation.yield(.sessionBecomeActive)
+ }
+ bridge.sessionDidInvalidateWithError = { [unowned self] in
+ eventStreamContinuation.yield(.sessionInvalidated(reason: $0))
+ eventStreamContinuation.finish()
+ }
+ bridge.sessionDidDetect = { [unowned self] in
+ eventStreamContinuation.yield(.sessionDetected(tags: $0))
+ }
+ eventStreamContinuation.yield(.sessionIsReady)
+ }
+
+ public let eventStream: EventStream
+
+ public var isReady: Bool {
+ bridge?.isReady ?? false
+ }
+
+ public var alertMessage: String {
+ get { bridge?.alertMessage ?? "" }
+ set {
+ guard let bridge = assertedBridge() else { return }
+ bridge.alertMessage = newValue
+ }
+ }
+
+ public func start() {
+ guard let bridge = assertedBridge() else { return }
+ bridge.begin()
+ eventStreamContinuation.yield(.sessionStarted)
+ }
+
+ public var connectedTag: NFCTag? {
+ guard let bridge = assertedBridge() else { return nil }
+ return bridge.connectedTag
+ }
+
+ public func connect(to tag: NFCTag) async throws {
+ guard let bridge = assertedBridge() else { return }
+ try await bridge.connect(to: tag)
+ }
+
+ public func stop() {
+ guard let bridge = assertedBridge() else { return }
+ bridge.invalidate()
+ }
+
+ public func stop(errorMessage: String) {
+ guard let bridge = assertedBridge() else { return }
+ bridge.invalidate(errorMessage: errorMessage)
+ }
+
+ private let eventStreamContinuation: EventStream.Continuation
+ private let bridge: NFCTagReaderSessionBridge?
+
+ private func assertedBridge(
+ file: StaticString = #file,
+ line: UInt = #line
+ ) -> NFCTagReaderSessionBridge? {
+ guard let bridge else {
+ assertionFailure("Please check the `reason` from `.sessionCreationFailed(reason:)` sent to `eventStream`.", file: file, line: line)
+ return nil
+ }
+ return bridge
+ }
+}
+
+extension AsyncNFCTagReaderSession {
+ public enum Event: AsyncNFCReaderSessionEvent {
+ public enum SessionCreationFailedReason: Sendable {
+ case pollingOptionIsEmpty
+ case systemNotAvailable
+ }
+
+ case sessionIsReady
+ case sessionStarted
+ case sessionBecomeActive
+ case sessionDetected(tags: [NFCTag])
+ case sessionCreationFailed(reason: SessionCreationFailedReason)
+ case sessionInvalidated(reason: NFCReaderError)
+ }
+}
+
+extension AsyncNFCTagReaderSession {
+ public typealias EventStream = AsyncStream
+}
+
+private final class NFCTagReaderSessionBridge: NSObject, NFCReaderSessionBridgeable, NFCTagReaderSessionDelegate {
+ private lazy var session: NFCTagReaderSession = { preconditionFailure("`session` has not been set.") }()
+ lazy var sessionDidBecomeActive: () -> Void = { preconditionFailure("`sessionDidBecomeActive` has not been set.") }()
+ lazy var sessionDidInvalidateWithError: (_ error: NFCReaderError) -> Void = { preconditionFailure("`sessionDidInvalidateWithError` has not been set.") }()
+ lazy var sessionDidDetect: (_ tags: [NFCTag]) -> Void = { preconditionFailure("`sessionDidDetect` has not been set.") }()
+
+ init?(
+ pollingOption: NFCTagReaderSession.PollingOption
+ ) {
+ super.init()
+ guard let session = NFCTagReaderSession(
+ pollingOption: pollingOption,
+ delegate: self,
+ queue: nil
+ ) else {
+ return nil
+ }
+ self.session = session
+ }
+
+ var isReady: Bool {
+ session.isReady
+ }
+
+ var alertMessage: String {
+ get { session.alertMessage }
+ set { session.alertMessage = newValue }
+ }
+
+ func begin() {
+ session.begin()
+ }
+
+ var connectedTag: NFCTag? {
+ session.connectedTag
+ }
+
+ func connect(to tag: NFCTag) async throws {
+ try await session.connect(to: tag)
+ }
+
+ func invalidate() {
+ session.invalidate()
+ }
+
+ func invalidate(errorMessage: String) {
+ session.invalidate(errorMessage: errorMessage)
+ }
+
+ func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
+ sessionDidBecomeActive()
+ }
+
+ func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: any Error) {
+ sessionDidInvalidateWithError(error as! NFCReaderError)
+ }
+
+ func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
+ sessionDidDetect(tags)
+ }
+}
+#endif
From 529641755f647b0d73f4edcbb751ffa41ff10ade Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:19:50 +0900
Subject: [PATCH 08/17] Create AsyncNFCVASReaderSession.swift
---
Sources/Async/AsyncNFCVASReaderSession.swift | 120 +++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100644 Sources/Async/AsyncNFCVASReaderSession.swift
diff --git a/Sources/Async/AsyncNFCVASReaderSession.swift b/Sources/Async/AsyncNFCVASReaderSession.swift
new file mode 100644
index 0000000..519576c
--- /dev/null
+++ b/Sources/Async/AsyncNFCVASReaderSession.swift
@@ -0,0 +1,120 @@
+//
+// AsyncNFCVASReaderSession.swift
+// Async
+//
+// Created by treastrain on 2024/01/28.
+//
+
+#if canImport(CoreNFC)
+open class AsyncNFCVASReaderSession: AsyncNFCReaderSession {
+ public init(
+ vasCommandConfigurations: [NFCVASCommandConfiguration]
+ ) {
+ (eventStream, eventStreamContinuation) = EventStream.makeStream()
+ bridge = .init(vasCommandConfigurations: vasCommandConfigurations)
+ bridge.sessionDidBecomeActive = { [unowned self] in
+ eventStreamContinuation.yield(.sessionBecomeActive)
+ }
+ bridge.sessionDidInvalidateWithError = { [unowned self] in
+ eventStreamContinuation.yield(.sessionInvalidated(reason: $0))
+ eventStreamContinuation.finish()
+ }
+ bridge.sessionDidReceive = { [unowned self] in
+ eventStreamContinuation.yield(.sessionReceived(responses: $0))
+ }
+ eventStreamContinuation.yield(.sessionIsReady)
+ }
+
+ public let eventStream: EventStream
+
+ public var isReady: Bool {
+ bridge.isReady
+ }
+
+ public var alertMessage: String {
+ get { bridge.alertMessage }
+ set { bridge.alertMessage = newValue }
+ }
+
+ public func start() {
+ bridge.begin()
+ eventStreamContinuation.yield(.sessionStarted)
+ }
+
+ public func stop() {
+ bridge.invalidate()
+ }
+
+ public func stop(errorMessage: String) {
+ bridge.invalidate(errorMessage: errorMessage)
+ }
+
+ private let bridge: NFCVASReaderSessionBridge
+ private let eventStreamContinuation: EventStream.Continuation
+}
+
+extension AsyncNFCVASReaderSession {
+ public enum Event: AsyncNFCReaderSessionEvent {
+ case sessionIsReady
+ case sessionStarted
+ case sessionBecomeActive
+ case sessionReceived(responses: [NFCVASResponse])
+ case sessionInvalidated(reason: NFCReaderError)
+ }
+}
+
+extension AsyncNFCVASReaderSession {
+ public typealias EventStream = AsyncStream
+}
+
+private final class NFCVASReaderSessionBridge: NSObject, NFCReaderSessionBridgeable, NFCVASReaderSessionDelegate {
+ private lazy var session: NFCVASReaderSession = { preconditionFailure("`session` has not been set.") }()
+ lazy var sessionDidBecomeActive: () -> Void = { preconditionFailure("`sessionDidBecomeActive` has not been set.") }()
+ lazy var sessionDidInvalidateWithError: (_ error: NFCReaderError) -> Void = { preconditionFailure("`sessionDidInvalidateWithError` has not been set.") }()
+ lazy var sessionDidReceive: (_ responses: [NFCVASResponse]) -> Void = { preconditionFailure("`sessionDidReceive` has not been set.") }()
+
+ init(
+ vasCommandConfigurations: [NFCVASCommandConfiguration]
+ ) {
+ super.init()
+ session = .init(
+ vasCommandConfigurations: vasCommandConfigurations,
+ delegate: self,
+ queue: nil
+ )
+ }
+
+ var isReady: Bool {
+ session.isReady
+ }
+
+ var alertMessage: String {
+ get { session.alertMessage }
+ set { session.alertMessage = newValue }
+ }
+
+ func begin() {
+ session.begin()
+ }
+
+ func invalidate() {
+ session.invalidate()
+ }
+
+ func invalidate(errorMessage: String) {
+ session.invalidate(errorMessage: errorMessage)
+ }
+
+ func readerSessionDidBecomeActive(_ session: NFCVASReaderSession) {
+ sessionDidBecomeActive()
+ }
+
+ func readerSession(_ session: NFCVASReaderSession, didInvalidateWithError error: any Error) {
+ sessionDidInvalidateWithError(error as! NFCReaderError)
+ }
+
+ func readerSession(_ session: NFCVASReaderSession, didReceive responses: [NFCVASResponse]) {
+ sessionDidReceive(responses)
+ }
+}
+#endif
From 74f4de62e51296b002be85db120da75f9d114a96 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:22:07 +0900
Subject: [PATCH 09/17] Update sample app deployment target to 15.0
---
Examples/TRETNFCKitExample.xcodeproj/project.pbxproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Examples/TRETNFCKitExample.xcodeproj/project.pbxproj b/Examples/TRETNFCKitExample.xcodeproj/project.pbxproj
index 6db9bff..6329379 100644
--- a/Examples/TRETNFCKitExample.xcodeproj/project.pbxproj
+++ b/Examples/TRETNFCKitExample.xcodeproj/project.pbxproj
@@ -322,7 +322,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -356,7 +356,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
From 1b0eb462d8002a7ed9ccfd0b3a5e5b073fc69239 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:37:54 +0900
Subject: [PATCH 10/17] Use `AsyncNFCNDEFMessageReaderSession` with
`NFCNDEFMessageReaderExampleView`
---
.../NFCNDEFMessageReaderExampleView.swift | 35 +++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/Examples/TRETNFCKitExample/NFCNDEFMessageReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCNDEFMessageReaderExampleView.swift
index 6d8d0ab..ce84ad4 100644
--- a/Examples/TRETNFCKitExample/NFCNDEFMessageReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCNDEFMessageReaderExampleView.swift
@@ -6,26 +6,34 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_NDEFMessage
struct NFCNDEFMessageReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCNDEFMessageReaderSession?
var body: some View {
List {
Button {
isPresented = true
} label: {
- Text("Read (using view modifier)")
+ Text("Read (using reader view modifier)")
}
Button {
Task {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCNDEFMessageReaderSession(invalidateAfterFirstRead: false)
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.nfcNDEFMessageReader(
isPresented: $isPresented,
@@ -45,6 +53,29 @@ struct NFCNDEFMessageReaderExampleView: View {
return .success(alertMessage: "Done!")
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCNDEFMessageReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let messages):
+ print(messages)
+ readerSession.alertMessage = "Done!"
+ readerSession.stop()
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("NDEF Messages")
}
}
From d4865ae5f2eb97184ae527fcfe573206aada4f0c Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:48:37 +0900
Subject: [PATCH 11/17] Use `AsyncNFCNDEFTagReaderSession` with
`NFCNDEFTagReaderExampleView`
---
.../NFCNDEFTagReaderExampleView.swift | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/Examples/TRETNFCKitExample/NFCNDEFTagReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCNDEFTagReaderExampleView.swift
index b19a71a..503725b 100644
--- a/Examples/TRETNFCKitExample/NFCNDEFTagReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCNDEFTagReaderExampleView.swift
@@ -6,11 +6,13 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_NDEFTag
struct NFCNDEFTagReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCNDEFTagReaderSession?
var body: some View {
List {
@@ -24,8 +26,14 @@ struct NFCNDEFTagReaderExampleView: View {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCNDEFTagReaderSession(invalidateAfterFirstRead: false)
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.nfcNDEFTagReader(
isPresented: $isPresented,
@@ -47,6 +55,35 @@ struct NFCNDEFTagReaderExampleView: View {
return .success
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCNDEFTagReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let tags):
+ do {
+ let tag = tags.first!
+ try await readerSession.connect(to: tag)
+ let message = try await tag.readNDEF()
+ readerSession.alertMessage = "\(message)"
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("NDEF Tag")
}
}
From f7f3fad11e6b524aff69246450a72a69a43e9ee7 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 00:59:53 +0900
Subject: [PATCH 12/17] Use `AsyncNFCTagReaderSession` with
`NFCNativeTagReaderExampleView`
---
.../NFCNativeTagReaderExampleView.swift | 51 ++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/Examples/TRETNFCKitExample/NFCNativeTagReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCNativeTagReaderExampleView.swift
index fc837fd..1435f87 100644
--- a/Examples/TRETNFCKitExample/NFCNativeTagReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCNativeTagReaderExampleView.swift
@@ -6,11 +6,13 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_NativeTag
struct NFCNativeTagReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCTagReaderSession?
var body: some View {
List {
@@ -24,8 +26,14 @@ struct NFCNativeTagReaderExampleView: View {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092])
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.nfcNativeTagReader(
isPresented: $isPresented,
@@ -58,6 +66,47 @@ struct NFCNativeTagReaderExampleView: View {
return .success
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCTagReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let tags):
+ do {
+ let tag = tags.first!
+ try await readerSession.connect(to: tag)
+ switch tag {
+ case .feliCa(let feliCaTag):
+ readerSession.alertMessage = "FeliCa\n\(feliCaTag.currentIDm as NSData)"
+ case .iso7816(let iso7816Tag):
+ readerSession.alertMessage = "ISO14443-4 type A / B tag with ISO7816\n\(iso7816Tag.identifier as NSData)"
+ case .iso15693(let iso15693Tag):
+ readerSession.alertMessage = "ISO 15693\n\(iso15693Tag.identifier as NSData)"
+ case .miFare(let miFareTag):
+ readerSession.alertMessage = "MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443\n\(miFareTag.identifier as NSData)"
+ @unknown default:
+ readerSession.alertMessage = "Unknown tag."
+ }
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionCreationFailed(let reason):
+ print(reason)
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("Multiple")
}
}
From 5fe38665e8c571b7d2c3e867c9d17ed495da657b Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 01:12:03 +0900
Subject: [PATCH 13/17] Use `AsyncNFCTagReaderSession` with
`NFCFeliCaTagReaderExampleView`
---
.../NFCFeliCaTagReaderExampleView.swift | 44 ++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/Examples/TRETNFCKitExample/NFCFeliCaTagReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCFeliCaTagReaderExampleView.swift
index b52f9f2..691edc2 100644
--- a/Examples/TRETNFCKitExample/NFCFeliCaTagReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCFeliCaTagReaderExampleView.swift
@@ -6,11 +6,13 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_FeliCa
struct NFCFeliCaTagReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCTagReaderSession?
var body: some View {
List {
@@ -24,8 +26,14 @@ struct NFCFeliCaTagReaderExampleView: View {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCTagReaderSession(pollingOption: .iso18092)
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.feliCaTagReader(
isPresented: $isPresented,
@@ -47,6 +55,40 @@ struct NFCFeliCaTagReaderExampleView: View {
return .success
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCTagReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let tags):
+ do {
+ let tag = tags.first!
+ guard case .feliCa(let feliCaTag) = tag else {
+ throw NFCReaderError(.readerErrorInvalidParameter)
+ }
+ try await readerSession.connect(to: tag)
+ let (idm, systemCode) = try await feliCaTag.polling(systemCode: Data([0xFE, 0x00]), requestCode: .systemCode, timeSlot: .max1)
+ readerSession.alertMessage = "\(systemCode as NSData)\n\(idm as NSData)"
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionCreationFailed(let reason):
+ print(reason)
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("FeliCa")
}
}
From e02b939f9e4abea1dac7375d21debd8a89e49cb4 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 01:16:12 +0900
Subject: [PATCH 14/17] Use `AsyncNFCTagReaderSession` with
`NFCISO7816TagReaderExampleView`
---
.../NFCISO7816TagReaderExampleView.swift | 43 ++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/Examples/TRETNFCKitExample/NFCISO7816TagReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCISO7816TagReaderExampleView.swift
index 0bde504..fd916b4 100644
--- a/Examples/TRETNFCKitExample/NFCISO7816TagReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCISO7816TagReaderExampleView.swift
@@ -6,11 +6,13 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_ISO7816
struct NFCISO7816TagReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCTagReaderSession?
var body: some View {
List {
@@ -24,8 +26,14 @@ struct NFCISO7816TagReaderExampleView: View {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCTagReaderSession(pollingOption: .iso14443)
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.iso7816TagReader(
isPresented: $isPresented,
@@ -46,6 +54,39 @@ struct NFCISO7816TagReaderExampleView: View {
return .success
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCTagReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let tags):
+ do {
+ let tag = tags.first!
+ guard case .iso7816(let iso7816Tag) = tag else {
+ throw NFCReaderError(.readerErrorInvalidParameter)
+ }
+ try await readerSession.connect(to: tag)
+ readerSession.alertMessage = "\(iso7816Tag.identifier as NSData)"
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionCreationFailed(let reason):
+ print(reason)
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("ISO 7816-compatible")
}
}
From 3de4ad2aec1e3ac6c3f88c8e104c631d508339da Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 01:19:40 +0900
Subject: [PATCH 15/17] Use `AsyncNFCTagReaderSession` with
`NFCISO15693TagReaderExampleView`
---
.../NFCISO15693TagReaderExampleView.swift | 43 ++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/Examples/TRETNFCKitExample/NFCISO15693TagReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCISO15693TagReaderExampleView.swift
index a257d31..f24698c 100644
--- a/Examples/TRETNFCKitExample/NFCISO15693TagReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCISO15693TagReaderExampleView.swift
@@ -6,11 +6,13 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_ISO15693
struct NFCISO15693TagReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCTagReaderSession?
var body: some View {
List {
@@ -24,8 +26,14 @@ struct NFCISO15693TagReaderExampleView: View {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCTagReaderSession(pollingOption: .iso15693)
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.iso15693TagReader(
isPresented: $isPresented,
@@ -46,6 +54,39 @@ struct NFCISO15693TagReaderExampleView: View {
return .success
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCTagReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let tags):
+ do {
+ let tag = tags.first!
+ guard case .iso15693(let iso15693Tag) = tag else {
+ throw NFCReaderError(.readerErrorInvalidParameter)
+ }
+ try await readerSession.connect(to: tag)
+ readerSession.alertMessage = "\(iso15693Tag.identifier as NSData)"
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionCreationFailed(let reason):
+ print(reason)
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("ISO 15693-compatible")
}
}
From a5bfe8e65e5042da3d960f08c5bfb79af07bbfab Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 01:25:05 +0900
Subject: [PATCH 16/17] Use `AsyncNFCTagReaderSession` with
`NFCMiFareTagReaderExampleView`
---
.../NFCMiFareTagReaderExampleView.swift | 43 ++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/Examples/TRETNFCKitExample/NFCMiFareTagReaderExampleView.swift b/Examples/TRETNFCKitExample/NFCMiFareTagReaderExampleView.swift
index b5e1083..7569a76 100644
--- a/Examples/TRETNFCKitExample/NFCMiFareTagReaderExampleView.swift
+++ b/Examples/TRETNFCKitExample/NFCMiFareTagReaderExampleView.swift
@@ -6,11 +6,13 @@
//
import SwiftUI
+import TRETNFCKit_Async
import TRETNFCKit_MiFare
struct NFCMiFareTagReaderExampleView: View {
@State private var isPresented = false
@ObservedObject var viewModel = ViewModel()
+ @State private var readerSession: AsyncNFCTagReaderSession?
var body: some View {
List {
@@ -24,8 +26,14 @@ struct NFCMiFareTagReaderExampleView: View {
try await viewModel.read()
}
} label: {
- Text("Read")
+ Text("Read (using reader)")
}
+ Button {
+ readerSession = AsyncNFCTagReaderSession(pollingOption: .iso14443)
+ } label: {
+ Text("Read (using async stream)")
+ }
+ .disabled(readerSession != nil)
}
.miFareTagReader(
isPresented: $isPresented,
@@ -46,6 +54,39 @@ struct NFCMiFareTagReaderExampleView: View {
return .success
}
)
+ .task(id: readerSession == nil) {
+ defer { readerSession = nil }
+ guard let readerSession else { return }
+ guard AsyncNFCTagReaderSession.readingAvailable else { return }
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ break
+ case .sessionBecomeActive:
+ break
+ case .sessionDetected(let tags):
+ do {
+ let tag = tags.first!
+ guard case .miFare(let miFareTag) = tag else {
+ throw NFCReaderError(.readerErrorInvalidParameter)
+ }
+ try await readerSession.connect(to: tag)
+ readerSession.alertMessage = "\(miFareTag.identifier as NSData)"
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionCreationFailed(let reason):
+ print(reason)
+ case .sessionInvalidated(let reason):
+ print(reason)
+ }
+ }
+ }
.navigationTitle("MiFare")
}
}
From 7520f7c66855dc866fe55db7a900cdfc58a52160 Mon Sep 17 00:00:00 2001
From: treastrain / Tanaka Ryoga
Date: Mon, 29 Jan 2024 02:52:13 +0900
Subject: [PATCH 17/17] Update README for async reader session wrappers
---
README.md | 203 +++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 184 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index 23416c5..a28c4f7 100644
--- a/README.md
+++ b/README.md
@@ -19,16 +19,28 @@ A wrapper for Core NFC and a useful helper when using NFC, leveraging Swift feat
[![Xcode Build & Test](https://github.com/treastrain/TRETJapanNFCReader/actions/workflows/xcodebuild.yml/badge.svg?branch=tretnfckit-main)](https://github.com/treastrain/TRETJapanNFCReader/actions/workflows/xcodebuild.yml)
[![Example App Build](https://github.com/treastrain/TRETJapanNFCReader/actions/workflows/xcodebuild_for_example_app.yml/badge.svg?branch=tretnfckit-main)](https://github.com/treastrain/TRETJapanNFCReader/actions/workflows/xcodebuild_for_example_app.yml)
-# Usage
-- ✅ No delegation pattern
- - When using Core NFC directly, it is usually a delegation pattern. In this case, this is unsafe because it is possible to forget to call a necessary command.
- - By using this wrapper, it can be converted to a closure pattern compatible with Swift Concurrency, and the Swift syntax prevents forgetting to call the necessary commands.
-- ✅ Support Swift Concurrency (async/await, Actor, Sendable)
- - It contains an Actor-wrapped [`NFCNDEFReaderSession`](https://developer.apple.com/documentation/corenfc/nfcndefreadersession)\, [`NFCTagReaderSession`](https://developer.apple.com/documentation/corenfc/nfctagreadersession)\, and [`NFCVASReaderSession`](https://developer.apple.com/documentation/corenfc/nfcvasreadersession)\, so they are safe for concurrency.
+# Features
+- ✅ (For beginners) A high-level wrapper for reader that can prevent forgetting to call necessary commands
+ - `FeliCaTagReader`: FeliCa (NFC-F)
+ - `ISO7816TagReader`: ISO 7816-compatible (NFC-A/B)
+ - `ISO15693TagReader`: ISO 15693-compatible (NFC-V)
+ - `MiFareTagReader`: MiFare (MIFARE Plus, UltraLight, DESFire) base on ISO 14443 (NFC-A)
+ - `NFCReader`: (for use directly)
+ - `NFCReader`
+ - `NFCReader`
+ - `NFCReader`
+- ✅ (For experts) A low-level wrapper for reader session that includes a conversion to the `AsyncSequence` pattern from the delegation pattern originally provided by Core NFC
+ - The asynchronous sequence is implemented in the following wrappers, which is very similar to [`CardSession.eventStream`](https://developer.apple.com/documentation/corenfc/cardsession/4318517-eventstream) (provided by Core NFC in iOS 17.4+, which allows communication with HCE-based NFC readers based on ISO 7816-4)
+ - `AsyncNFCNDEFMessageReaderSession` (for [`NFCNDEFReaderSession`](https://developer.apple.com/documentation/corenfc/nfcndefreadersession) with [`NFCNDEFMessage`](https://developer.apple.com/documentation/corenfc/nfcndeftag)s)
+ - `AsyncNFCNDEFTagReaderSession` (for [`NFCNDEFReaderSession`](https://developer.apple.com/documentation/corenfc/nfcndefreadersession) with [`NFCNDEFTag`](https://developer.apple.com/documentation/corenfc/nfcndefmessage)s)
+ - `AsyncNFCTagReaderSession` (for [`NFCTagReaderSession`](https://developer.apple.com/documentation/corenfc/nfctagreadersession))
+ - `AsyncNFCVASReaderSession` (for [`NFCVASReaderSession`](https://developer.apple.com/documentation/corenfc/nfcvasreadersession))
- ✅ Support SwiftUI
-## Native Tags (FeliCa (NFC-F), ISO 7816-compatible (NFC-A/B), ISO 15693-compatible (NFC-V), MiFare (NFC-A))
-### FeliCa (NFC-F)
+# Usage
+## High-level wrappers
+### Native Tags (FeliCa (NFC-F), ISO 7816-compatible (NFC-A/B), ISO 15693-compatible (NFC-V), MiFare (NFC-A))
+#### FeliCa (NFC-F)
```swift
import TRETNFCKit_FeliCa
@@ -45,7 +57,7 @@ try await reader.read(
}
```
-#### for SwiftUI
+##### for SwiftUI
```swift
import SwiftUI
import TRETNFCKit_FeliCa
@@ -64,7 +76,7 @@ Text("some view")
)
```
-### ISO 7816-compatible (NFC-A/B)
+#### ISO 7816-compatible (NFC-A/B)
```swift
import TRETNFCKit_ISO7816
@@ -81,7 +93,7 @@ try await reader.read(
}
```
-#### for SwiftUI
+##### for SwiftUI
```swift
import SwiftUI
import TRETNFCKit_ISO7816
@@ -100,7 +112,7 @@ Text("some view")
)
```
-### ISO 15693-compatible (NFC-V)
+#### ISO 15693-compatible (NFC-V)
```swift
import TRETNFCKit_ISO15693
@@ -117,7 +129,7 @@ try await reader.read(
}
```
-#### for SwiftUI
+##### for SwiftUI
```swift
import SwiftUI
import TRETNFCKit_ISO15693
@@ -136,7 +148,7 @@ Text("some view")
)
```
-### MiFare (MIFARE Plus, UltraLight, DESFire) base on ISO 14443 (NFC-A)
+#### MiFare (MIFARE Plus, UltraLight, DESFire) base on ISO 14443 (NFC-A)
```swift
import TRETNFCKit_MiFare
@@ -153,7 +165,7 @@ try await reader.read(
}
```
-#### for SwiftUI
+##### for SwiftUI
```swift
import SwiftUI
import TRETNFCKit_MiFare
@@ -172,7 +184,7 @@ Text("some view")
)
```
-### Use directly
+#### Use directly
```swift
import TRETNFCKit_NativeTag
@@ -198,7 +210,7 @@ try await reader.read(
)
```
-#### for SwiftUI
+##### for SwiftUI
```swift
import SwiftUI
import TRETNFCKit_NativeTag
@@ -226,7 +238,7 @@ Text("some view")
)
```
-## NDEF Tags
+### NDEF Tags
```swift
import TRETNFCKit_NDEFTag
@@ -262,7 +274,7 @@ Text("some view")
)
```
-## NDEF Messages
+### NDEF Messages
```swift
import TRETNFCKit_NDEFMessage
@@ -294,6 +306,159 @@ Text("some view")
)
```
+## Low-level wrappers
+
+### `AsyncNFCTagReaderSession` (for [`NFCTagReaderSession`](https://developer.apple.com/documentation/corenfc/nfctagreadersession))
+```swift
+import TRETNFCKit_Async
+
+func asyncNFCTagReaderSessionSample() async {
+ guard AsyncNFCTagReaderSession.readingAvailable else {
+ return
+ }
+
+ let readerSession = AsyncNFCTagReaderSession(
+ pollingOption: // ...
+ )
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ // ..
+ case .sessionBecomeActive:
+ // ..
+ case .sessionDetected(let tags):
+ let tag = tags.first!
+ do {
+ try await readerSession.connect(to: tag)
+ // ...
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionCreationFailed(let reason):
+ // ..
+ case .sessionInvalidated(let reason):
+ // ..
+ }
+ }
+}
+```
+
+#### with SwiftUI
+See `Examples/TRETNFCKitExample/NFCNativeTagReaderExampleView.swift`.
+
+### `AsyncNFCNDEFTagReaderSession` (for [`NFCNDEFReaderSession`](https://developer.apple.com/documentation/corenfc/nfcndefreadersession) with [`NFCNDEFTag`](https://developer.apple.com/documentation/corenfc/nfcndefmessage)s)
+```swift
+import TRETNFCKit_Async
+
+func asyncNFCNDEFTagReaderSessionSample() async {
+ guard AsyncNFCNDEFTagReaderSession.readingAvailable else {
+ return
+ }
+
+ let readerSession = AsyncNFCNDEFTagReaderSession(
+ invalidateAfterFirstRead: // ...
+ )
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ // ...
+ case .sessionBecomeActive:
+ // ...
+ case .sessionDetected(let tags):
+ let tag = tags.first!
+ do {
+ try await readerSession.connect(to: tag)
+ // ...
+ readerSession.stop()
+ } catch {
+ readerSession.stop(errorMessage: error.localizedDescription)
+ }
+ case .sessionInvalidated(let reason):
+ // ...
+ }
+ }
+}
+```
+
+#### with SwiftUI
+See `Examples/TRETNFCKitExample/NFCNDEFTagReaderExampleView.swift`.
+
+### `AsyncNFCNDEFMessageReaderSession` (for [`NFCNDEFReaderSession`](https://developer.apple.com/documentation/corenfc/nfcndefreadersession) with [`NFCNDEFMessage`](https://developer.apple.com/documentation/corenfc/nfcndeftag)s)
+```swift
+import TRETNFCKit_Async
+
+func asyncNFCNDEFMessageReaderSessionSample() async {
+ guard AsyncNFCNDEFMessageReaderSession.readingAvailable else {
+ return
+ }
+
+ let readerSession = AsyncNFCNDEFMessageReaderSession(
+ invalidateAfterFirstRead: // ...
+ )
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ // ...
+ case .sessionBecomeActive:
+ // ...
+ case .sessionDetected(let messages):
+ // ...
+ readerSession.stop()
+ case .sessionInvalidated(let reason):
+ // ...
+ }
+ }
+}
+```
+
+#### with SwiftUI
+See `Examples/TRETNFCKitExample/NFCNDEFMessageReaderExampleView.swift`.
+
+### `AsyncNFCVASReaderSession` (for [`NFCVASReaderSession`](https://developer.apple.com/documentation/corenfc/nfcvasreadersession))
+```swift
+import TRETNFCKit_Async
+
+func asyncNFCVASReaderSessionSample() async {
+ guard AsyncNFCVASReaderSession.readingAvailable else {
+ return
+ }
+
+ let readerSession = AsyncNFCVASReaderSession(
+ vasCommandConfigurations: // ...
+ )
+
+ for await event in readerSession.eventStream {
+ switch event {
+ case .sessionIsReady:
+ readerSession.alertMessage = "Place the tag on a flat, non-metal surface and rest your iPhone on the tag."
+ readerSession.start()
+ case .sessionStarted:
+ // ...
+ case .sessionBecomeActive:
+ // ...
+ case .sessionReceived(let responses):
+ // ...
+ readerSession.stop()
+ case .sessionInvalidated(let reason):
+ // ...
+ }
+ }
+}
+```
+
# Availability
- iOS 13.0+
- iPadOS 13.0+