From ea213906d6a3e35b30909433f537af809afe05e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Tue, 17 Mar 2026 21:05:01 +0100 Subject: [PATCH] Fix cannot share screenshots --- QuickDrop.xcodeproj/project.pbxproj | 4 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- ShareExtension/Helpers/Attachments.swift | 221 ++++++++++++------ 3 files changed, 157 insertions(+), 70 deletions(-) diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 97d732c..3dda83a 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -1774,7 +1774,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SHARED_BUILD = 137; - SHARED_VERSION = 2.14; + SHARED_VERSION = 2.14.1; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1837,7 +1837,7 @@ MTL_FAST_MATH = YES; SDKROOT = macosx; SHARED_BUILD = 137; - SHARED_VERSION = 2.14; + SHARED_VERSION = 2.14.1; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f737625..4f57d63 100644 --- a/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -124,7 +124,7 @@ "location" : "https://github.com/siteline/swiftui-introspect", "state" : { "branch" : "main", - "revision" : "fe758545cf980ab1085779d4a321a50d33b00f61" + "revision" : "26986a57e31c813f98262ddb62d4b07d40008535" } }, { diff --git a/ShareExtension/Helpers/Attachments.swift b/ShareExtension/Helpers/Attachments.swift index 6b3f296..b726099 100644 --- a/ShareExtension/Helpers/Attachments.swift +++ b/ShareExtension/Helpers/Attachments.swift @@ -11,6 +11,8 @@ import SwiftUI import LUI #if os(macOS) import Cocoa +#elseif canImport(UIKit) +import UIKit #endif func loadAttachments(with extensionContext: NSExtensionContext?, loadedItems: @escaping (AttachmentDetails) -> Void) { @@ -42,7 +44,31 @@ func loadAttachments(with extensionContext: NSExtensionContext?, loadedItems: @e ignoredAttachments += 1 } - checkIfAttachmentsLoaded(result: result, attachmentCount: attachments.count, ignoredAttachments: ignoredAttachments) + checkIfAttachmentsLoaded(result: result, + attachmentCount: attachments.count, + ignoredAttachments: ignoredAttachments, + item: item, + loadedItems: loadedItems) + } + } + else if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + let imageTypeIdentifier = attachment.registeredTypeIdentifiers.first(where: { + UTType($0)?.conforms(to: .image) == true + }) ?? UTType.image.identifier + attachment.loadItem(forTypeIdentifier: imageTypeIdentifier, options: nil) { data, _ in + if let url = imageUrl(from: data, typeIdentifier: imageTypeIdentifier) { + result.urls += [url] + } + else { + log("Attachment is not an image URL or data, ignoring. All attachments: \(attachments.description)") + ignoredAttachments += 1 + } + + checkIfAttachmentsLoaded(result: result, + attachmentCount: attachments.count, + ignoredAttachments: ignoredAttachments, + item: item, + loadedItems: loadedItems) } } else { @@ -52,20 +78,28 @@ func loadAttachments(with extensionContext: NSExtensionContext?, loadedItems: @e log("Found NSURL: \(url)") result.urls += [(url as URL)] - checkIfAttachmentsLoaded(result: result, attachmentCount: attachments.count, ignoredAttachments: ignoredAttachments) + checkIfAttachmentsLoaded(result: result, + attachmentCount: attachments.count, + ignoredAttachments: ignoredAttachments, + item: item, + loadedItems: loadedItems) } else { log("Attachment is not a URL, ignoring. All attachments: \(attachments.description)") ignoredAttachments += 1 - checkIfAttachmentsLoaded(result: result, attachmentCount: attachments.count, ignoredAttachments: ignoredAttachments) + checkIfAttachmentsLoaded(result: result, + attachmentCount: attachments.count, + ignoredAttachments: ignoredAttachments, + item: item, + loadedItems: loadedItems) } } } } } else { - loadTextIfPossible() + deliverTextIfPossible(from: item, loadedItems: loadedItems) } } else { @@ -74,83 +108,136 @@ func loadAttachments(with extensionContext: NSExtensionContext?, loadedItems: @e let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) extensionContext.cancelRequest(withError: cancelError) } +} + + +private func deliverTextIfPossible(from item: NSExtensionItem, loadedItems: @escaping (AttachmentDetails) -> Void) { + guard let details = textAttachmentDetails(from: item) else { return } + DispatchQueue.main.async { + loadedItems(details) + } +} + + +private func textAttachmentDetails(from item: NSExtensionItem) -> AttachmentDetails? { + guard let text = item.attributedContentText?.string else { return nil } + log("Found text content: \(text)") - func loadTextIfPossible() { - if let text = item.attributedContentText?.string { - - log("Found text content: \(text)") - - result.textToSend = text - - #if os(macOS) - result.previewImage = NSImage(named: NSImage.multipleDocumentsName) - #else - result.previewImage = Image(systemName: "text.document") - #endif - - let maxLength = 50 - let cleanedText = text.replacingOccurrences(of: "\n", with: " ").replacingOccurrences(of: "\r", with: "") - - if cleanedText.count > maxLength { - let index = cleanedText.index(cleanedText.startIndex, offsetBy: maxLength) - result.shortDescription = String(cleanedText[.. maxLength { + let index = cleanedText.index(cleanedText.startIndex, offsetBy: maxLength) + result.shortDescription = String(cleanedText[.. Void) { - func checkIfAttachmentsLoaded(result: AttachmentDetails, attachmentCount: Int, ignoredAttachments: Int = 0) { + var result = result + + if result.urls.count == attachmentCount-ignoredAttachments { - var result = result - - if result.urls.count == attachmentCount-ignoredAttachments { + // Unable to load anything useful, check if there is text as last resort + if result.urls.count == 0 { + deliverTextIfPossible(from: item, loadedItems: loadedItems) + return + } + else if result.urls.count == 1 { - // Unable to load anything useful, check if there is text as last resort - if result.urls.count == 0 { - loadTextIfPossible() - return - } - else if result.urls.count == 1 { + if result.urls[0].isFileURL { - if result.urls[0].isFileURL { - - log("Single file URL found: \(result.urls[0])") - - result.shortDescription = result.urls[0].lastPathComponent - #if os(macOS) - result.previewImage = NSWorkspace.shared.icon(forFile: result.urls[0].path) - #else - result.previewImage = Image(systemName: "document") - #endif - } - else if result.urls[0].scheme == "http" || result.urls[0].scheme == "https" { - result.shortDescription = result.urls[0].absoluteString - #if os(macOS) - result.previewImage = NSImage(named: NSImage.networkName) - #else - result.previewImage = Image(systemName: "network") - #endif - } - } - else { - result.shortDescription = String.localizedStringWithFormat("NFiles".localized(), result.urls.count) + log("Single file URL found: \(result.urls[0])") + + result.shortDescription = result.urls[0].lastPathComponent #if os(macOS) - result.previewImage = NSImage(named: NSImage.multipleDocumentsName) + result.previewImage = NSWorkspace.shared.icon(forFile: result.urls[0].path) #else result.previewImage = Image(systemName: "document") #endif } - - DispatchQueue.main.async { - loadedItems(result) + else if result.urls[0].scheme == "http" || result.urls[0].scheme == "https" { + result.shortDescription = result.urls[0].absoluteString + #if os(macOS) + result.previewImage = NSImage(named: NSImage.networkName) + #else + result.previewImage = Image(systemName: "network") + #endif } } + else { + result.shortDescription = String.localizedStringWithFormat("NFiles".localized(), result.urls.count) + #if os(macOS) + result.previewImage = NSImage(named: NSImage.multipleDocumentsName) + #else + result.previewImage = Image(systemName: "document") + #endif + } + + DispatchQueue.main.async { + loadedItems(result) + } + } +} + + +private func imageUrl(from data: Any?, typeIdentifier: String) -> URL? { + if let url = data as? NSURL { + log("Found image URL: \(url)") + return url as URL + } + + if let data = data as? Data { + let fileExtension = UTType(typeIdentifier)?.preferredFilenameExtension ?? "png" + return writeDataToTemporaryFile(data: data, fileExtension: fileExtension) + } + + #if canImport(UIKit) + if let image = data as? UIImage, + let imageData = image.pngData() { + return writeDataToTemporaryFile(data: imageData, fileExtension: "png") + } + #elseif os(macOS) + if let image = data as? NSImage, + let tiff = image.tiffRepresentation, + let rep = NSBitmapImageRep(data: tiff), + let pngData = rep.representation(using: .png, properties: [:]) { + return writeDataToTemporaryFile(data: pngData, fileExtension: "png") + } + #endif + + return nil +} + + +private func writeDataToTemporaryFile(data: Data, fileExtension: String) -> URL? { + let sanitizedExtension = fileExtension.isEmpty ? "dat" : fileExtension + let tempUrl = FileManager.default.temporaryDirectory + .appendingPathComponent("\(UUID().uuidString).\(sanitizedExtension)") + do { + try data.write(to: tempUrl) + return tempUrl + } + catch { + log("Failed to write attachment data to temp file: \(error)") + return nil } }