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+