forked from jsnbuchanan/crowd-funder-for-time-pwa
refactor(ios): preserve original image formats in share extension
Remove JPEG conversion and preserve original image formats and filenames to test support for all image file types. Refactor image processing logic for clarity and maintainability. Changes: - Remove forced JPEG conversion (was using 0.9 compression quality) - Preserve original filename extensions instead of forcing .jpg - Refactor from 3 branches to 2 (removed unlikely UIImage branch) - Replace if statement with guard for non-image attachment filtering - Simplify error check from optional binding to nil check - Extract filename extension helper method to reduce duplication - Update default filename from "shared-image.jpg" to "shared-image" The only conversion that may occur is to PNG (lossless) when raw data cannot be read from a file URL, preserving image quality while ensuring compatibility.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user