2f7669b15b
Erster Kompilierlauf der Datei ueberhaupt - vorher hing sie in keiner Sources-Phase und wurde nie uebersetzt. Die deutschen Anfuehrungszeichen in der Hinweismeldung waren gemischt: oeffnend typografisch (U+201E), schliessend aber gerade (U+0022). In einem Swift-String beendet das gerade Zeichen die Zeichenkette, der Rest der Zeile war Unsinn. Jetzt beidseitig typografisch. In Zeile 97 stehen nur noch die zwei geraden Anfuehrungszeichen des Strings selbst.
158 lines
6.0 KiB
Swift
158 lines
6.0 KiB
Swift
//
|
|
// 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 gelesen und Base64-kodiert.
|
|
// 2. Es landet zusammen mit seinem Namen in den geteilten
|
|
// UserDefaults der Gruppe `group.app.datametric360`.
|
|
// 3. Die Erweiterung öffnet die App über ihr eigenes URL-Schema.
|
|
// 4. Die App liest den Eintrag beim Start bzw. beim Zurückkehren aus,
|
|
// löscht ihn und lädt den Beleg hoch (daten/geteilterBeleg.ts).
|
|
//
|
|
// Base64 in UserDefaults statt einer Datei im Gruppen-Container: die App
|
|
// liest UserDefaults ohnehin schon über @capacitor/preferences, das genau
|
|
// diese Gruppe kennt. Eine Datei im Container bräuchte dagegen eigenen
|
|
// nativen Code auf der App-Seite — ein zusätzliches Plugin für einen Weg,
|
|
// den es sonst nicht gäbe.
|
|
//
|
|
// Der Preis ist eine Größengrenze: UserDefaults ist für Einstellungen
|
|
// gedacht, nicht für Anhänge. GRENZE_BYTES hält das bei 4 MB. Ein
|
|
// Tankbeleg liegt bei einigen Dutzend Kilobyte, ein gescanntes Mehrseiten-
|
|
// PDF kann darüber liegen — dann sagt die Erweiterung das und der Weg über
|
|
// „Datei auswählen" in der App bleibt.
|
|
//
|
|
// 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"
|
|
|
|
/// Schlüssel, unter dem die App den Beleg abholt (geteilterBeleg.ts).
|
|
private let schluessel = "dm360_geteilter_beleg"
|
|
|
|
/// 4 MB. UserDefaults ist kein Dateispeicher — darüber wird abgelehnt,
|
|
/// statt das System mit einem Anhang zu belasten, für den es nicht
|
|
/// gedacht ist.
|
|
private let grenzeBytes = 4 * 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 ablage = UserDefaults(suiteName: gruppe) else {
|
|
return melden("Die App-Gruppe ist nicht eingerichtet.")
|
|
}
|
|
// Form wie in geteilterBeleg.ts erwartet. @capacitor/preferences legt
|
|
// Werte als String ab, deshalb JSON und kein Dictionary.
|
|
let inhalt: [String: Any] = [
|
|
"name": name,
|
|
"base64": daten.base64EncodedString(),
|
|
"zeit": Int(Date().timeIntervalSince1970 * 1000),
|
|
]
|
|
guard
|
|
let json = try? JSONSerialization.data(withJSONObject: inhalt),
|
|
let text = String(data: json, encoding: .utf8)
|
|
else {
|
|
return melden("Der Beleg ließ sich nicht übergeben.")
|
|
}
|
|
ablage.set(text, forKey: schluessel)
|
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.appOeffnen()
|
|
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() {
|
|
guard let ziel = URL(string: "datametric360://beleg") 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)
|
|
}
|
|
}
|