// // ShareViewController.swift // TimeSafariShareExtension // // Created by Aardimus on 11/24/25. // import UIKit import Social import UniformTypeIdentifiers class ShareViewController: SLComposeServiceViewController { private let appGroupIdentifier = "group.app.timesafari" private let sharedPhotoBase64Key = "sharedPhotoBase64" private let sharedPhotoFileNameKey = "sharedPhotoFileName" override func viewDidLoad() { super.viewDidLoad() // Set placeholder text (required for SLComposeServiceViewController) self.placeholder = "Share image to TimeSafari" // Validate content on load self.validateContent() } override func isContentValid() -> Bool { // Validate that we have image attachments guard let extensionContext = extensionContext else { return false } guard let inputItems = extensionContext.inputItems as? [NSExtensionItem] else { return false } for item in inputItems { if let attachments = item.attachments { for attachment in attachments { if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { return true } } } } return false } override func didSelectPost() { // Extract and process the shared image guard let extensionContext = extensionContext else { return } guard let inputItems = extensionContext.inputItems as? [NSExtensionItem] else { extensionContext.completeRequest(returningItems: [], completionHandler: nil) return } // Process the first image found processSharedImage(from: inputItems) { [weak self] success in guard let self = self else { extensionContext.completeRequest(returningItems: [], completionHandler: nil) return } if success { // Open the main app via deep link self.openMainApp() } // Complete the extension context extensionContext.completeRequest(returningItems: [], completionHandler: nil) } } private func processSharedImage(from items: [NSExtensionItem], completion: @escaping (Bool) -> Void) { // Find the first image attachment for item in items { guard let attachments = item.attachments else { continue } for attachment in attachments { if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { 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 { completion(false) return } // Handle different image data types var imageData: Data? var fileName: String = "shared-image.jpg" if let url = data as? URL { // Image provided as file URL imageData = try? Data(contentsOf: url) fileName = url.lastPathComponent } else if let image = data as? UIImage { // Image provided as UIImage imageData = image.jpegData(compressionQuality: 0.9) fileName = "shared-image.jpg" } else if let data = data as? Data { // Image provided as raw Data imageData = data fileName = "shared-image.jpg" } guard let finalImageData = imageData else { completion(false) return } // Convert to base64 let base64String = finalImageData.base64EncodedString() // Store in App Group UserDefaults guard let userDefaults = UserDefaults(suiteName: self.appGroupIdentifier) else { completion(false) return } userDefaults.set(base64String, forKey: self.sharedPhotoBase64Key) userDefaults.set(fileName, forKey: self.sharedPhotoFileNameKey) userDefaults.synchronize() completion(true) } return // Process only the first image } } } // No image found completion(false) } private func openMainApp() { // Open the main app via deep link guard let url = URL(string: "timesafari://shared-photo") else { return } var responder: UIResponder? = self while responder != nil { if let application = responder as? UIApplication { application.open(url, options: [:], completionHandler: nil) return } responder = responder?.next } // Fallback: use extension context extensionContext?.open(url, completionHandler: nil) } override func configurationItems() -> [Any]! { // No additional configuration options needed return [] } }