diff --git a/ios/Sources/NFCTag.swift b/ios/Sources/NFCTag.swift index 9d131e7..419424c 100644 --- a/ios/Sources/NFCTag.swift +++ b/ios/Sources/NFCTag.swift @@ -99,6 +99,48 @@ final class NFCDienst: NSObject { } } + /// Kurzfassung eines CoreNFC-Fehlers. + /// + /// Apples ``localizedDescription`` ist hier unbrauchbar – sie liefert Dinge + /// wie „Stack Error", die niemandem sagen, was zu tun ist. Fast alle Faelle + /// sind voruebergehend: Tag zu frueh weg, Funkkontakt verloren, Stack belegt. + /// + /// Ein unbekannter Code behaelt seine Nummer. Damit steht beim naechsten + /// Auftreten sofort fest, welcher Fall hier noch fehlt. + static func kurz(_ fehler: Error) -> String { + guard let code = (fehler as? NFCReaderError)?.code else { + return "Fehlgeschlagen." + } + switch code { + case .readerTransceiveErrorTagConnectionLost: + return "Tag zu früh entfernt." + case .readerTransceiveErrorRetryExceeded: + return "Keine Antwort vom Tag." + case .readerTransceiveErrorTagResponseError: + return "Tag antwortet fehlerhaft." + case .readerTransceiveErrorTagNotConnected: + return "Kein Tag verbunden." + case .readerSessionInvalidationErrorSessionTimeout: + return "Zeit abgelaufen." + case .readerSessionInvalidationErrorSystemIsBusy: + return "NFC gerade belegt." + case .readerSessionInvalidationErrorSessionTerminatedUnexpectedly: + return "Sitzung abgebrochen." + case .ndefReaderSessionErrorTagUpdateFailure: + return "Schreiben fehlgeschlagen." + case .ndefReaderSessionErrorTagNotWritable: + return "Tag nicht beschreibbar." + case .readerErrorRadioDisabled: + return "NFC ist aus." + case .readerErrorSecurityViolation: + return "NFC nicht erlaubt." + case .readerErrorUnsupportedFeature: + return "Tag-Typ nicht unterstützt." + default: + return "Fehlgeschlagen (Code \(code.rawValue))." + } + } + private enum Auftrag { case lesen case schreiben(nutzlast: String, sperren: Bool) @@ -155,8 +197,10 @@ final class NFCDienst: NSObject { if let meldung { session?.alertMessage = meldung session?.invalidate() - } else if case .failure = ergebnis { - session?.invalidate() + } else if case let .failure(fehler) = ergebnis { + // Mit Text: Ohne ihn schliesst sich Apples Blatt wortlos und die + // Meldung steht erst danach in der App. + session?.invalidate(errorMessage: fehler.localizedDescription) } session = nil fortsetzung.resume(with: ergebnis) @@ -166,7 +210,7 @@ final class NFCDienst: NSObject { private func lesen(_ tag: NFCNDEFTag) { tag.readNDEF { nachricht, fehler in if let fehler { - self.fertig(.failure(Fehler.system(fehler.localizedDescription))) + self.fertig(.failure(Fehler.system(Self.kurz(fehler)))) return } guard let text = nachricht?.records.compactMap(Self.text(aus:)).first, !text.isEmpty else { @@ -187,7 +231,7 @@ final class NFCDienst: NSObject { tag.queryNDEFStatus { status, kapazitaet, fehler in if let fehler { - self.fertig(.failure(Fehler.system(fehler.localizedDescription))) + self.fertig(.failure(Fehler.system(Self.kurz(fehler)))) return } switch status { @@ -210,7 +254,7 @@ final class NFCDienst: NSObject { tag.writeNDEF(nachricht) { fehler in if let fehler { - self.fertig(.failure(Fehler.system(fehler.localizedDescription))) + self.fertig(.failure(Fehler.system(Self.kurz(fehler)))) return } guard sperren else { @@ -224,8 +268,8 @@ final class NFCDienst: NSObject { // Das gehört gesagt, statt den ganzen Vorgang als // gescheitert auszugeben. self.fertig(.failure(Fehler.system( - "Tag beschrieben, aber das Sperren ist fehlgeschlagen: " - + fehler.localizedDescription))) + "Tag beschrieben, Sperren fehlgeschlagen: " + + Self.kurz(fehler)))) return } AudioServicesPlaySystemSound(1057) @@ -274,7 +318,7 @@ extension NFCDienst: NFCTagReaderSessionDelegate { } session.connect(to: ersteser) { fehler in if let fehler { - self.fertig(.failure(Fehler.system(fehler.localizedDescription))) + self.fertig(.failure(Fehler.system(Self.kurz(fehler)))) return } switch self.auftrag { @@ -293,6 +337,6 @@ extension NFCDienst: NFCTagReaderSessionDelegate { let abbruch = (error as? NFCReaderError)?.code == .readerSessionInvalidationErrorUserCanceled self.session = nil - fertig(.failure(abbruch ? Fehler.abgebrochen : Fehler.system(error.localizedDescription))) + fertig(.failure(abbruch ? Fehler.abgebrochen : Fehler.system(Self.kurz(error)))) } } diff --git a/ios/Sources/NFCViews.swift b/ios/Sources/NFCViews.swift index 11d42d9..868030f 100644 --- a/ios/Sources/NFCViews.swift +++ b/ios/Sources/NFCViews.swift @@ -33,30 +33,40 @@ struct NFCSchreibenView: View { Text("Auf das Tag wird geschrieben: \(nutzlast)") } - Section { - Toggle("Tag dauerhaft schreibschützen", isOn: $sperren) - } footer: { - if sperren { - Text("Das lässt sich nicht rückgängig machen. Das Tag gehört danach " - + "für immer zu diesem Einzelstück und kann nicht neu beschrieben werden.") - .foregroundStyle(.orange) - } else { - Text("Ohne Schreibschutz kannst du das Tag später einem anderen " - + "Einzelstück zuweisen.") + // Nach dem Erfolg hat das Tag seinen Inhalt. Knopf und Schalter + // stehenzulassen hiesse, es noch einmal beschreiben zu wollen. + if !erfolg { + Section { + Toggle("Tag dauerhaft schreibschützen", isOn: $sperren) + } footer: { + if sperren { + Text("Das lässt sich nicht rückgängig machen. Das Tag gehört danach " + + "für immer zu diesem Einzelstück und kann nicht neu beschrieben werden.") + .foregroundStyle(.orange) + } else { + Text("Ohne Schreibschutz kannst du das Tag später einem anderen " + + "Einzelstück zuweisen.") + } } - } - Section { - Button(laeuft ? "Tag anhalten…" : "Tag beschreiben") { - Task { await schreiben() } + Section { + Button(laeuft ? "Tag anhalten…" : "Tag beschreiben") { + Task { await schreiben() } + } + .disabled(laeuft) } - .disabled(laeuft) } if erfolg { Section { - Label("Tag beschrieben.", systemImage: "checkmark.circle") + Label(sperren ? "Tag beschrieben und gesperrt." : "Tag beschrieben.", + systemImage: "checkmark.circle") .foregroundStyle(.green) + // Hier ist nichts mehr zu tun; „Fertig" gehoert deshalb in + // den Inhalt und nicht nur oben in die Leiste. Ohne das + // stuende man im Anlege-Ablauf vor einem fast leeren Blatt. + Button("Fertig") { if let onFertig { onFertig() } else { dismiss() } } + .buttonStyle(.borderedProminent) } } if let fehler {