diff --git a/ios/Sources/Info.plist b/ios/Sources/Info.plist
index f9fbd87..d4c5518 100644
--- a/ios/Sources/Info.plist
+++ b/ios/Sources/Info.plist
@@ -2,123 +2,91 @@
-
-
- CFBundleDevelopmentRegion
- de
- CFBundleLocalizations
-
- de
-
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- APPL
-
- CFBundleDisplayName
- Vorrania
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
-
-
- LSApplicationCategoryType
- public.app-category.utilities
-
-
- ITSAppUsesNonExemptEncryption
-
-
- UILaunchScreen
-
-
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
- NSCameraUsageDescription
- Die Kamera wird zum Scannen von Barcodes beim Ein- und Auslagern verwendet.
-
-
- NFCReaderUsageDescription
- NFC wird zum Lesen und Beschreiben der Tags an Einzelstücken verwendet.
-
-
- NSAppTransportSecurity
-
- NSAllowsLocalNetworking
-
-
-
-
- UIBackgroundModes
-
- fetch
-
- BGTaskSchedulerPermittedIdentifiers
-
- com.scarriffle.vorrania.refresh
-
-
-
- UIApplicationShortcutItems
-
-
- UIApplicationShortcutItemType
- com.scarriffle.vorrania.checkin
- UIApplicationShortcutItemTitle
- Einlagern
- UIApplicationShortcutItemIconType
- UIApplicationShortcutIconTypeAdd
-
-
- UIApplicationShortcutItemType
- com.scarriffle.vorrania.checkout
- UIApplicationShortcutItemTitle
- Auslagern
- UIApplicationShortcutItemIconType
- UIApplicationShortcutIconTypeRemove
-
-
-
-
- CFBundleURLTypes
-
-
- CFBundleURLName
- com.scarriffle.vorrania
- CFBundleURLSchemes
-
- vorrania
-
-
-
+ BGTaskSchedulerPermittedIdentifiers
+
+ com.scarriffle.vorrania.refresh
+
+ CFBundleDevelopmentRegion
+ de
+ CFBundleDisplayName
+ Vorrania
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleLocalizations
+
+ de
+
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ com.scarriffle.vorrania
+ CFBundleURLSchemes
+
+ vorrania
+
+
+
+ CFBundleVersion
+ 1
+ ITSAppUsesNonExemptEncryption
+
+ LSApplicationCategoryType
+ public.app-category.utilities
+ NFCReaderUsageDescription
+ NFC wird zum Lesen und Beschreiben der Tags an Einzelstücken verwendet.
+ NSAppTransportSecurity
+
+ NSAllowsLocalNetworking
+
+
+ NSCameraUsageDescription
+ Die Kamera wird zum Scannen von Barcodes beim Ein- und Auslagern verwendet.
+ UIApplicationShortcutItems
+
+
+ UIApplicationShortcutItemIconType
+ UIApplicationShortcutIconTypeAdd
+ UIApplicationShortcutItemTitle
+ Einlagern
+ UIApplicationShortcutItemType
+ com.scarriffle.vorrania.checkin
+
+
+ UIApplicationShortcutItemIconType
+ UIApplicationShortcutIconTypeRemove
+ UIApplicationShortcutItemTitle
+ Auslagern
+ UIApplicationShortcutItemType
+ com.scarriffle.vorrania.checkout
+
+
+ UIBackgroundModes
+
+ fetch
+
+ UILaunchScreen
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
diff --git a/ios/Sources/ItemViews.swift b/ios/Sources/ItemViews.swift
index 37e116c..55a6695 100644
--- a/ios/Sources/ItemViews.swift
+++ b/ios/Sources/ItemViews.swift
@@ -214,7 +214,7 @@ struct ItemEditView: View {
LabeledContent("UID", value: item.uid)
// Für Kleidung: ein eingenähtes NFC-Tag hält, was ein aufgeklebter
// QR nicht übersteht. Auf dem iPad ausgeblendet – dort gibt es kein NFC.
- if NFCTag.verfuegbar {
+ if NFCDienst.verfuegbar {
Button { nfcShown = true } label: {
Label("NFC-Tag beschreiben", systemImage: "wave.3.right")
}
diff --git a/ios/Sources/NFCTag.swift b/ios/Sources/NFCTag.swift
index dc1dfb4..9d131e7 100644
--- a/ios/Sources/NFCTag.swift
+++ b/ios/Sources/NFCTag.swift
@@ -50,17 +50,25 @@ func einzelstueckCode(aus roh: String, nackteUidErlauben: Bool = false) -> Strin
/// aussen sieht das hier wie ein normales `await` aus, damit die Views genauso
/// aussehen wie beim Kamera-Scan.
///
+/// **Warum `NFCTagReaderSession` und nicht `NFCNDEFReaderSession`:** Seit dem
+/// SDK von iOS 26/27 lehnt die App-Store-Prüfung den Entitlement-Wert `NDEF` ab
+/// („NDEF is disallowed") und verlangt `TAG`. `TAG` ist der Wert für diese
+/// Sitzungsart. Die NDEF-Befehle bleiben trotzdem verfügbar: alle vier
+/// Tag-Protokolle (MiFare, ISO7816, ISO15693, FeliCa) erben von `NFCNDEFTag`,
+/// also gibt es weiterhin `queryNDEFStatus`, `readNDEF`, `writeNDEF` und
+/// `writeLock`.
+///
/// Gelesen wird nur mit geöffneter App. iOS startet Apps beim Antippen im
/// Hintergrund ausschliesslich über https-Universal-Links auf einer beanspruchten
/// Domain – eigene Schemes ignoriert es dabei stillschweigend. Eine feste Domain
/// wäre für einen selbst gehosteten Dienst aber gerade der falsche Weg (siehe
/// Kommentar an `Router.handle(url:)`).
-final class NFCTag: NSObject {
+final class NFCDienst: NSObject {
/// Ob das Gerät NFC lesen kann. Auf dem iPad immer `false` – die App läuft
/// dort ebenfalls (`TARGETED_DEVICE_FAMILY "1,2"`), deshalb muss jede
/// NFC-Bedienung daran hängen.
- static var verfuegbar: Bool { NFCNDEFReaderSession.readingAvailable }
+ static var verfuegbar: Bool { NFCTagReaderSession.readingAvailable }
enum Fehler: LocalizedError {
case nichtVerfuegbar
@@ -97,38 +105,42 @@ final class NFCTag: NSObject {
}
private let queue = DispatchQueue(label: "com.scarriffle.vorrania.nfc")
- private var session: NFCNDEFReaderSession?
+ private var session: NFCTagReaderSession?
private var auftrag: Auftrag = .lesen
private var weiter: CheckedContinuation?
/// Die Sitzung hält sich selbst am Leben, solange sie läuft – sonst gibt der
/// Aufrufer die Instanz frei und das Delegate meldet sich nie.
- private var festhalten: NFCTag?
+ private var festhalten: NFCDienst?
// MARK: Öffentlich
/// Liest ein Tag und gibt seinen Inhalt roh zurück.
static func lesen() async throws -> String {
- try await NFCTag().starten(.lesen, hinweis: "Halte das Tag an die Oberseite des iPhones.")
+ try await NFCDienst().starten(.lesen)
}
/// Beschreibt ein Tag. `sperren` macht es **unwiderruflich** schreibgeschützt.
static func schreiben(_ nutzlast: String, sperren: Bool = false) async throws {
- _ = try await NFCTag().starten(
- .schreiben(nutzlast: nutzlast, sperren: sperren),
- hinweis: "Halte das Tag an die Oberseite des iPhones."
- )
+ _ = try await NFCDienst().starten(.schreiben(nutzlast: nutzlast, sperren: sperren))
}
// MARK: Ablauf
- private func starten(_ auftrag: Auftrag, hinweis: String) async throws -> String {
+ private func starten(_ auftrag: Auftrag) async throws -> String {
guard Self.verfuegbar else { throw Fehler.nichtVerfuegbar }
self.auftrag = auftrag
return try await withCheckedThrowingContinuation { fortsetzung in
+ // iso14443 deckt die üblichen NTAG-/MIFARE-Aufkleber ab, iso15693 die
+ // Wäschetags, die manche Hersteller stattdessen verbauen.
+ guard let s = NFCTagReaderSession(
+ pollingOption: [.iso14443, .iso15693], delegate: self, queue: queue
+ ) else {
+ fortsetzung.resume(throwing: Fehler.nichtVerfuegbar)
+ return
+ }
weiter = fortsetzung
festhalten = self
- let s = NFCNDEFReaderSession(delegate: self, queue: queue, invalidateAfterFirstRead: false)
- s.alertMessage = hinweis
+ s.alertMessage = "Halte das Tag an die Oberseite des iPhones."
session = s
s.begin()
}
@@ -230,38 +242,51 @@ final class NFCTag: NSObject {
if let text = record.wellKnownTypeTextPayload().0 { return text }
return String(data: record.payload, encoding: .utf8)
}
+
+ /// Alle vier Tag-Arten erben von `NFCNDEFTag` – die NDEF-Befehle gibt es
+ /// also unabhängig davon, welche Technik das Tag verwendet.
+ private static func ndef(aus tag: NFCTag) -> NFCNDEFTag? {
+ switch tag {
+ case let .miFare(t): return t
+ case let .iso7816(t): return t
+ case let .iso15693(t): return t
+ case let .feliCa(t): return t
+ @unknown default: return nil
+ }
+ }
}
-// MARK: - NFCNDEFReaderSessionDelegate
+// MARK: - NFCTagReaderSessionDelegate
-extension NFCTag: NFCNDEFReaderSessionDelegate {
+extension NFCDienst: NFCTagReaderSessionDelegate {
- /// Wird nicht aufgerufen, solange `didDetect tags:` vorhanden ist – Core NFC
- /// nimmt dann den Weg über die Tags. Das Protokoll verlangt die Methode
- /// trotzdem.
- func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {}
+ func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {}
- func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
- guard tags.count == 1, let tag = tags.first else {
+ func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
+ guard tags.count == 1, let ersteser = tags.first else {
session.alertMessage = "Mehrere Tags erkannt. Bitte nur eines anhalten."
session.restartPolling()
return
}
- session.connect(to: tag) { fehler in
+ guard let ndef = Self.ndef(aus: ersteser) else {
+ fertig(.failure(Fehler.nichtBeschreibbar))
+ return
+ }
+ session.connect(to: ersteser) { fehler in
if let fehler {
self.fertig(.failure(Fehler.system(fehler.localizedDescription)))
return
}
switch self.auftrag {
case .lesen:
- self.lesen(tag)
+ self.lesen(ndef)
case let .schreiben(nutzlast, sperren):
- self.schreiben(tag, nutzlast: nutzlast, sperren: sperren)
+ self.schreiben(ndef, nutzlast: nutzlast, sperren: sperren)
}
}
}
- func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
+ func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
// Hat der Vorgang schon ein Ergebnis geliefert, ist das hier nur noch das
// Aufräumen danach.
guard weiter != nil else { return }
diff --git a/ios/Sources/NFCViews.swift b/ios/Sources/NFCViews.swift
index 97416fa..8157c2a 100644
--- a/ios/Sources/NFCViews.swift
+++ b/ios/Sources/NFCViews.swift
@@ -71,9 +71,9 @@ struct NFCSchreibenView: View {
erfolg = false
defer { laeuft = false }
do {
- try await NFCTag.schreiben(nutzlast, sperren: sperren)
+ try await NFCDienst.schreiben(nutzlast, sperren: sperren)
erfolg = true
- } catch NFCTag.Fehler.abgebrochen {
+ } catch NFCDienst.Fehler.abgebrochen {
// Kein Fehler, sondern eine Entscheidung – nichts anzeigen.
} catch {
fehler = error.localizedDescription
@@ -93,7 +93,7 @@ struct NFCLesenButton: View {
@State private var laeuft = false
var body: some View {
- if NFCTag.verfuegbar {
+ if NFCDienst.verfuegbar {
Button {
Task { await lesen() }
} label: {
@@ -107,7 +107,7 @@ struct NFCLesenButton: View {
laeuft = true
defer { laeuft = false }
do {
- let roh = try await NFCTag.lesen()
+ let roh = try await NFCDienst.lesen()
// Ein selbst beschriebenes Tag darf auch die nackte UID tragen – anders
// als bei der Kamera droht hier keine Verwechslung mit einem Barcode.
guard let uid = einzelstueckCode(aus: roh, nackteUidErlauben: true) else {
@@ -115,7 +115,7 @@ struct NFCLesenButton: View {
return
}
onUid(uid)
- } catch NFCTag.Fehler.abgebrochen {
+ } catch NFCDienst.Fehler.abgebrochen {
// Nutzer hat abgebrochen – keine Meldung.
} catch {
onFehler(error.localizedDescription)
diff --git a/ios/Sources/Vorrania.entitlements b/ios/Sources/Vorrania.entitlements
index 5f7b942..2bb4dee 100644
--- a/ios/Sources/Vorrania.entitlements
+++ b/ios/Sources/Vorrania.entitlements
@@ -4,7 +4,7 @@
com.apple.developer.nfc.readersession.formats
- NDEF
+ TAG
diff --git a/ios/project.yml b/ios/project.yml
index 809b5ff..f76e23a 100644
--- a/ios/project.yml
+++ b/ios/project.yml
@@ -63,5 +63,9 @@ targets:
entitlements:
path: Sources/Vorrania.entitlements
properties:
+ # "TAG", nicht "NDEF": Die App-Store-Pruefung lehnt NDEF seit dem
+ # SDK von iOS 26/27 ab ("NDEF is disallowed") und verlangt TAG. Deshalb
+ # laeuft der Code ueber NFCTagReaderSession; die NDEF-Befehle gibt es
+ # dort weiterhin, weil alle Tag-Protokolle von NFCNDEFTag erben.
com.apple.developer.nfc.readersession.formats:
- - NDEF
+ - TAG