share extension now accepts raw image data and falls back to a file copy, so sharing from the markup editor no longer closes immediately
This commit is contained in:
1 parent
c5592f117b
commit
4a4e101532
1 file changed
+90
-17
@@ -107,20 +107,58 @@ final class ShareViewController: UIViewController {
|
||||
(UTType.url.identifier, .url),
|
||||
(UTType.data.identifier, .file),
|
||||
]
|
||||
for (typeId, kind) in order {
|
||||
if provider.hasItemConformingToTypeIdentifier(typeId) {
|
||||
provider.loadItem(forTypeIdentifier: typeId) { [weak self] data, error in
|
||||
guard let self else { completion(nil); return }
|
||||
if error != nil { completion(nil); return }
|
||||
completion(self.toSharedFile(data: data, kind: kind))
|
||||
for (typeId, kind) in order where provider.hasItemConformingToTypeIdentifier(typeId) {
|
||||
// Konkreter Typ (z. B. public.png statt public.image) für Dateiendung
|
||||
// und MIME-Type, falls die Quelle nur rohe Daten liefert.
|
||||
let concrete = concreteType(of: provider, conformingTo: typeId)
|
||||
provider.loadItem(forTypeIdentifier: typeId) { [weak self] data, error in
|
||||
guard let self else { completion(nil); return }
|
||||
if error == nil,
|
||||
let file = self.toSharedFile(data: data, kind: kind,
|
||||
concrete: concrete, provider: provider) {
|
||||
completion(file)
|
||||
return
|
||||
}
|
||||
return
|
||||
// Manche Quellen (z. B. der Screenshot-/Markup-Editor) liefern über
|
||||
// loadItem nichts Verwertbares oder eine URL außerhalb unserer
|
||||
// Sandbox. loadFileRepresentation stellt eine lesbare Kopie bereit.
|
||||
self.loadFileFallback(provider: provider, kind: kind,
|
||||
concrete: concrete, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
private func toSharedFile(data: Any?, kind: SharedMediaType) -> SharedMediaFile? {
|
||||
private func concreteType(of provider: NSItemProvider, conformingTo typeId: String) -> UTType? {
|
||||
guard let base = UTType(typeId) else { return nil }
|
||||
return provider.registeredTypeIdentifiers
|
||||
.compactMap { UTType($0) }
|
||||
.first { $0.conforms(to: base) } ?? base
|
||||
}
|
||||
|
||||
private func loadFileFallback(provider: NSItemProvider, kind: SharedMediaType,
|
||||
concrete: UTType?,
|
||||
completion: @escaping (SharedMediaFile?) -> Void) {
|
||||
guard kind == .image || kind == .video || kind == .file, let concrete else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
provider.loadFileRepresentation(forTypeIdentifier: concrete.identifier) { [weak self] url, _ in
|
||||
// Die URL ist nur innerhalb dieses Callbacks gültig — sofort kopieren.
|
||||
guard let self, let url, let dst = self.copyIntoAppGroup(src: url) else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
let mime = self.mime(for: url) ?? concrete.preferredMIMEType
|
||||
let duration = kind == .video ? self.videoDurationMs(url: dst) : nil
|
||||
completion(SharedMediaFile(path: self.pathString(for: dst), mimeType: mime,
|
||||
thumbnail: nil, duration: duration, message: nil, type: kind))
|
||||
}
|
||||
}
|
||||
|
||||
private func toSharedFile(data: Any?, kind: SharedMediaType, concrete: UTType?,
|
||||
provider: NSItemProvider) -> SharedMediaFile? {
|
||||
switch kind {
|
||||
case .text:
|
||||
guard let s = data as? String else { return nil }
|
||||
@@ -139,6 +177,10 @@ final class ShareViewController: UIViewController {
|
||||
return SharedMediaFile(path: pathString(for: dst), mimeType: "image/png",
|
||||
thumbnail: nil, duration: nil, message: nil, type: .image)
|
||||
}
|
||||
if let d = data as? Data, let dst = writeData(d, type: concrete, provider: provider) {
|
||||
return SharedMediaFile(path: pathString(for: dst), mimeType: concrete?.preferredMIMEType,
|
||||
thumbnail: nil, duration: nil, message: nil, type: .image)
|
||||
}
|
||||
return nil
|
||||
case .video:
|
||||
guard let u = data as? URL, let dst = copyIntoAppGroup(src: u) else { return nil }
|
||||
@@ -146,9 +188,15 @@ final class ShareViewController: UIViewController {
|
||||
thumbnail: nil, duration: videoDurationMs(url: u),
|
||||
message: nil, type: .video)
|
||||
case .file:
|
||||
guard let u = data as? URL, let dst = copyIntoAppGroup(src: u) else { return nil }
|
||||
return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
|
||||
thumbnail: nil, duration: nil, message: nil, type: .file)
|
||||
if let u = data as? URL, let dst = copyIntoAppGroup(src: u) {
|
||||
return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
|
||||
thumbnail: nil, duration: nil, message: nil, type: .file)
|
||||
}
|
||||
if let d = data as? Data, let dst = writeData(d, type: concrete, provider: provider) {
|
||||
return SharedMediaFile(path: pathString(for: dst), mimeType: concrete?.preferredMIMEType,
|
||||
thumbnail: nil, duration: nil, message: nil, type: .file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,13 +240,11 @@ final class ShareViewController: UIViewController {
|
||||
}
|
||||
|
||||
private func copyIntoShareDir(src: URL) -> URL? {
|
||||
guard let dir = shareDir else { return nil }
|
||||
let name = src.lastPathComponent.isEmpty ? UUID().uuidString : src.lastPathComponent
|
||||
var dst = dir.appendingPathComponent(name)
|
||||
// Mehrfachauswahl mit gleichem Namen innerhalb eines Shares.
|
||||
if FileManager.default.fileExists(atPath: dst.path) {
|
||||
dst = dir.appendingPathComponent("\(UUID().uuidString.prefix(8))-\(name)")
|
||||
}
|
||||
guard let dst = uniqueDestination(name: name) else { return nil }
|
||||
// Security-scoped URLs (z. B. aus "Dateien") sind sonst nicht lesbar.
|
||||
let scoped = src.startAccessingSecurityScopedResource()
|
||||
defer { if scoped { src.stopAccessingSecurityScopedResource() } }
|
||||
do {
|
||||
try FileManager.default.copyItem(at: src, to: dst)
|
||||
return dst
|
||||
@@ -207,6 +253,33 @@ final class ShareViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// Nur auf fileQueue aufrufen.
|
||||
private func uniqueDestination(name: String) -> URL? {
|
||||
guard let dir = shareDir else { return nil }
|
||||
let dst = dir.appendingPathComponent(name)
|
||||
// Mehrfachauswahl mit gleichem Namen innerhalb eines Shares.
|
||||
if FileManager.default.fileExists(atPath: dst.path) {
|
||||
return dir.appendingPathComponent("\(UUID().uuidString.prefix(8))-\(name)")
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
private func writeData(_ data: Data, type: UTType?, provider: NSItemProvider) -> URL? {
|
||||
let ext = type?.preferredFilenameExtension ?? "bin"
|
||||
var base = (provider.suggestedName ?? UUID().uuidString)
|
||||
.replacingOccurrences(of: "/", with: "-")
|
||||
if (base as NSString).pathExtension.lowercased() == ext.lowercased() {
|
||||
base = (base as NSString).deletingPathExtension
|
||||
}
|
||||
// Namenswahl und Schreiben zusammen seriell, sonst können zwei Anhänge
|
||||
// mit gleichem suggestedName denselben Pfad bekommen.
|
||||
return fileQueue.sync {
|
||||
guard let dst = uniqueDestination(name: "\(base).\(ext)"),
|
||||
(try? data.write(to: dst)) != nil else { return nil }
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
||||
private func writePng(image: UIImage) -> URL? {
|
||||
guard let data = image.pngData(),
|
||||
let dir = fileQueue.sync(execute: { shareDir }) else { return nil }
|
||||
|
||||
Reference in new issue
Block a user