diff --git a/ios/App/TimeSafariShareExtension/ShareViewController.swift b/ios/App/TimeSafariShareExtension/ShareViewController.swift index cd6c733a..bf2296ac 100644 --- a/ios/App/TimeSafariShareExtension/ShareViewController.swift +++ b/ios/App/TimeSafariShareExtension/ShareViewController.swift @@ -13,7 +13,7 @@ class ShareViewController: UIViewController { private let appGroupIdentifier = "group.app.timesafari" private let sharedPhotoFileNameKey = "sharedPhotoFileName" private let sharedPhotoFilePathKey = "sharedPhotoFilePath" - private let sharedImageFileName = "shared-image.jpg" + private let sharedImageFileName = "shared-image" /// Get the App Group container URL for storing shared files private var appGroupContainerURL: URL? { @@ -71,68 +71,31 @@ class ShareViewController: UIViewController { } for attachment in attachments { - if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { - // First, try to load as UIImage directly (best for HEIF and other formats) - // This uses the system's built-in image decoding which handles HEIF automatically - if attachment.canLoadObject(ofClass: UIImage.self) { - attachment.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in - guard let self = self else { - completion(false) - return - } - - if let error = error { - completion(false) - return - } - - guard let image = image as? UIImage else { - completion(false) - return - } - - // Convert to JPEG for web compatibility - guard let imageData = image.jpegData(compressionQuality: 0.9) else { - completion(false) - return - } - - // Get filename from attachment if available - var fileName = "shared-image.jpg" - if let identifier = attachment.suggestedName { - let nameWithoutExt = (identifier as NSString).deletingPathExtension - fileName = "\(nameWithoutExt).jpg" - } - - // Store image as file in App Group container - if self.storeImageData(imageData, fileName: fileName) { - completion(true) - } else { - completion(false) - } - } - return // Process only the first image - } - - // Fallback: load as data/URL (for formats that don't support UIImage loading) - attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] (data, error) in + // Skip non-image attachments + guard attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) else { + continue + } + + // Try to load raw data first to preserve original format + // This preserves the original image format without conversion + attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] (data, error) in guard let self = self else { completion(false) return } - if let error = error { + if error != nil { completion(false) return } // Handle different image data types + // loadItem(forTypeIdentifier:) typically returns URL or Data, not UIImage var imageData: Data? - var fileName: String = "shared-image.jpg" + var fileName: String = "shared-image" if let url = data as? URL { - // Image provided as file URL - // For security-scoped resources, we need to access them properly + // Most common case: Image provided as file URL - read raw data to preserve format let accessing = url.startAccessingSecurityScopedResource() defer { if accessing { @@ -140,32 +103,19 @@ class ShareViewController: UIViewController { } } - // Try loading as UIImage first (handles HEIF, RAW, etc.) - if let image = UIImage(contentsOfFile: url.path) { - // Convert to JPEG for web compatibility - imageData = image.jpegData(compressionQuality: 0.9) - // Preserve original filename but change extension to .jpg - let originalName = url.lastPathComponent - if let nameWithoutExt = originalName.components(separatedBy: ".").first { - fileName = "\(nameWithoutExt).jpg" - } else { - fileName = "shared-image.jpg" - } - } else { - // Fallback: try reading raw data (for already web-compatible formats) - imageData = try? Data(contentsOf: url) - fileName = url.lastPathComponent + // Read raw data directly to preserve original format + imageData = try? Data(contentsOf: url) + fileName = url.lastPathComponent + + // Fallback: if raw data read fails, try UIImage and convert to PNG (lossless) + if imageData == nil, let image = UIImage(contentsOfFile: url.path) { + imageData = image.pngData() + fileName = self.getFileNameWithExtension(url.lastPathComponent, newExtension: "png") } } else if let data = data as? Data { - // Image provided as raw Data - // Try to load as UIImage first to handle HEIF/RAW formats - if let image = UIImage(data: data) { - imageData = image.jpegData(compressionQuality: 0.9) - } else { - // Fallback: use raw data (assumes already web-compatible) - imageData = data - } - fileName = "shared-image.jpg" + // Less common: Image provided as raw Data - use directly to preserve format + imageData = data + fileName = attachment.suggestedName ?? "shared-image" } guard let finalImageData = imageData else { @@ -181,7 +131,6 @@ class ShareViewController: UIViewController { } } return // Process only the first image - } } } @@ -189,6 +138,14 @@ class ShareViewController: UIViewController { completion(false) } + /// Helper to get filename with a new extension, preserving base name + private func getFileNameWithExtension(_ originalName: String, newExtension: String) -> String { + if let nameWithoutExt = originalName.components(separatedBy: ".").first, !nameWithoutExt.isEmpty { + return "\(nameWithoutExt).\(newExtension)" + } + return "shared-image.\(newExtension)" + } + /// Store image data as a file in the App Group container /// All images are stored as files regardless of size for consistency and simplicity /// Returns true if successful, false otherwise @@ -197,8 +154,10 @@ class ShareViewController: UIViewController { return false } - // Create file URL in the container - let fileURL = containerURL.appendingPathComponent(sharedImageFileName) + // Create file URL in the container using the actual filename + // Extract extension from fileName if present, otherwise use sharedImageFileName + let actualFileName = fileName.isEmpty ? sharedImageFileName : fileName + let fileURL = containerURL.appendingPathComponent(actualFileName) // Remove old file if it exists try? FileManager.default.removeItem(at: fileURL) @@ -216,7 +175,7 @@ class ShareViewController: UIViewController { } // Store relative path and filename - userDefaults.set(sharedImageFileName, forKey: sharedPhotoFilePathKey) + userDefaults.set(actualFileName, forKey: sharedPhotoFilePathKey) userDefaults.set(fileName, forKey: sharedPhotoFileNameKey) // Clean up any old base64 data that might exist