// // ShareViewController.swift // DataMetric360Share // // Nimmt ein PDF aus dem iOS-Teilen-Blatt entgegen (typischerweise ein // Tankbeleg aus Mail) und reicht es an die App weiter. // // WIE DIE ÜBERGABE FUNKTIONIERT — UND WARUM SO // -------------------------------------------- // Eine Share-Erweiterung ist ein eigener Prozess mit eigenem Dateibereich. // Sie kann der App nichts direkt geben; der einzige gemeinsame Boden ist // eine App-Gruppe. Der Weg hier: // // 1. Das PDF wird in den Container der App-Gruppe geschrieben. // 2. Die Erweiterung öffnet die App über ihr URL-Schema und gibt den // Pfad mit: `datametric360://beleg?pfad=`. // 3. Die App liest die Datei über @capacitor/filesystem (das volle // file://-Pfade ausdrücklich unterstützt) und löscht sie danach. // // DER ERSTE ENTWURF GING ÜBER USERDEFAULTS UND HAT NIE FUNKTIONIERT. // Er legte das PDF base64-kodiert unter `dm360_geteilter_beleg` in // `UserDefaults(suiteName: gruppe)` ab, weil die App UserDefaults über // @capacitor/preferences ohnehin liest. Nur liest dieses Plugin (8.0.1, // ios/Sources/PreferencesPlugin/Preferences.swift) IMMER // `UserDefaults.standard` der App — `configure({ group })` wechselt nicht // den Container, sondern setzt nur ein Schlüssel-Präfix. Die App suchte // also `group.app.datametric360.dm360_geteilter_beleg` in ihrem eigenen // Bereich, während hier der plain Schlüssel im Gruppen-Container lag: // falscher Behälter und falscher Schlüssel. Gemeldet am 02.09.2026 // („es wird kein Tankvorgang angelegt beim Teilen mit der App“). // // Die Datei hat zwei weitere Vorteile: keine Base64-Aufblähung um ein // Drittel, und keine 4-MB-Grenze — UserDefaults ist für Einstellungen // gedacht, nicht für Anhänge. Geblieben ist eine Obergrenze, aber eine // großzügige (GRENZE_BYTES). // // DIESE DATEI IST VERSIONIERT, ios/ NICHT. // `npx cap add ios` erzeugt den nativen Ordner neu und würde alles darin // verlieren. scripts/ios-teilen-einrichten.mjs kopiert diese Datei bei // jedem Bau wieder hinein und hängt das Ziel ins Xcode-Projekt. // import UIKit import Social import UniformTypeIdentifiers class ShareViewController: UIViewController { /// Muss zur App-Gruppe in beiden Entitlements-Dateien passen. private let gruppe = "group.app.datametric360" /// Unterordner im Gruppen-Container, in dem geteilte Belege liegen. private let ordner = "geteilte-belege" /// 24 MB. Kein technisches Limit mehr, sondern eine Vernunftgrenze: /// ein Tankbeleg liegt bei einigen Dutzend Kilobyte, alles jenseits /// davon ist kein Beleg, sondern ein Versehen. private let grenzeBytes = 24 * 1024 * 1024 override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .clear pdfUebernehmen() } private func pdfUebernehmen() { guard let eintrag = (extensionContext?.inputItems as? [NSExtensionItem])?.first, let anhaenge = eintrag.attachments else { return abschliessen() } let pdfTyp = UTType.pdf.identifier guard let anhang = anhaenge.first(where: { $0.hasItemConformingToTypeIdentifier(pdfTyp) }) else { return melden("Kein PDF dabei.") } anhang.loadItem(forTypeIdentifier: pdfTyp, options: nil) { [weak self] wert, _ in guard let self else { return } // Je nach Quelle kommt eine URL, ein Data-Objekt oder nichts // Brauchbares an — Mail liefert eine URL auf eine temporäre // Kopie, andere Apps reichen die Bytes direkt. var daten: Data? var name = "beleg.pdf" if let url = wert as? URL { daten = try? Data(contentsOf: url) name = url.lastPathComponent } else if let roh = wert as? Data { daten = roh } guard let daten else { return self.melden("Der Beleg ließ sich nicht lesen.") } guard daten.count <= self.grenzeBytes else { return self.melden( "Der Beleg ist zu groß für diesen Weg. In der App über „Beleg hochladen“ → „Datei auswählen“.") } self.ablegen(daten: daten, name: name) } } private func ablegen(daten: Data, name: String) { guard let container = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: gruppe) else { return melden("Die App-Gruppe ist nicht eingerichtet.") } let ziel = container.appendingPathComponent(ordner, isDirectory: true) // Ein Zeitstempel im Namen: zwei rasch nacheinander geteilte Belege // sollen sich nicht gegenseitig überschreiben, und der Name bleibt // für den Nutzer lesbar, falls er den Container je zu sehen bekommt. let datei = ziel.appendingPathComponent( "\(Int(Date().timeIntervalSince1970))-\(name)") do { try FileManager.default.createDirectory( at: ziel, withIntermediateDirectories: true) try daten.write(to: datei, options: .atomic) } catch { return melden("Der Beleg ließ sich nicht übergeben.") } DispatchQueue.main.async { [weak self] in self?.appOeffnen(pfad: datei) self?.abschliessen() } } /// Öffnet die Host-App. Eine Erweiterung darf `UIApplication.shared` nicht /// benutzen, deshalb der Umweg über die Responder-Kette — der einzige von /// Apple vorgesehene Weg aus einer Erweiterung heraus. private func appOeffnen(pfad: URL) { // Der Pfad wandert als Abfrageparameter mit. Er zeigt in den // Gruppen-Container, den die App mit derselben Berechtigung lesen // darf - kein zusätzliches Plugin nötig. var bau = URLComponents() bau.scheme = "datametric360" bau.host = "beleg" bau.queryItems = [URLQueryItem(name: "pfad", value: pfad.absoluteString)] guard let ziel = bau.url else { return } var antwort: UIResponder? = self while let r = antwort { if let app = r as? UIApplication { app.open(ziel, options: [:], completionHandler: nil) return } antwort = r.next } } private func melden(_ text: String) { DispatchQueue.main.async { [weak self] in let hinweis = UIAlertController(title: "DataMetric360", message: text, preferredStyle: .alert) hinweis.addAction(UIAlertAction(title: "Verstanden", style: .default) { _ in self?.abschliessen() }) self?.present(hinweis, animated: true) } } private func abschliessen() { extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } }