// // ShareViewController.swift // AnyDropShare // // Share Extension that receives photos/videos (and other files) from the // iOS Share Sheet, stashes them into the App Group shared container, and // opens the main AnyDrop app via the custom URL scheme so the webview can // consume them. // import UIKit import Social import MobileCoreServices import UniformTypeIdentifiers private let appGroupId = "group.fr.arthurbarre.anydrop" class ShareViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(white: 0.96, alpha: 1.0) processSharedItems() } private func processSharedItems() { guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else { finish() return } let group = DispatchGroup() var savedFiles: [[String: String]] = [] let savedFilesLock = NSLock() for item in extensionItems { guard let attachments = item.attachments else { continue } for provider in attachments { group.enter() loadFile(provider: provider) { fileInfo in if let info = fileInfo { savedFilesLock.lock() savedFiles.append(info) savedFilesLock.unlock() } group.leave() } } } group.notify(queue: .main) { [weak self] in self?.writeManifest(files: savedFiles) self?.openHostApp() self?.finish() } } private func loadFile( provider: NSItemProvider, completion: @escaping ([String: String]?) -> Void ) { let candidates: [UTType] = [.image, .movie, .audio, .pdf, .fileURL, .data] let match = candidates.first { provider.hasItemConformingToTypeIdentifier($0.identifier) } guard let utType = match else { completion(nil); return } provider.loadItem(forTypeIdentifier: utType.identifier, options: nil) { data, _ in self.persist(item: data, hintType: utType, completion: completion) } } private func persist( item: Any?, hintType: UTType, completion: @escaping ([String: String]?) -> Void ) { guard let container = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: appGroupId ) else { completion(nil); return } let inbox = container.appendingPathComponent("shared-inbox", isDirectory: true) try? FileManager.default.createDirectory(at: inbox, withIntermediateDirectories: true) let uuid = UUID().uuidString func write(data: Data, ext: String, mime: String, name: String) { let fileName = name.isEmpty ? "\(uuid).\(ext)" : name let dest = inbox.appendingPathComponent("\(uuid)-\(fileName)") do { try data.write(to: dest) completion([ "id": uuid, "path": dest.lastPathComponent, "name": fileName, "mime": mime, "size": String(data.count), ]) } catch { completion(nil) } } if let url = item as? URL { do { let data = try Data(contentsOf: url) let name = url.lastPathComponent let ext = url.pathExtension.isEmpty ? hintType.preferredFilenameExtension ?? "bin" : url.pathExtension let mime = hintType.preferredMIMEType ?? mimeFromPath(url: url) write(data: data, ext: ext, mime: mime, name: name) } catch { completion(nil) } return } if let image = item as? UIImage, let data = image.jpegData(compressionQuality: 0.95) { write(data: data, ext: "jpg", mime: "image/jpeg", name: "") return } if let data = item as? Data { let ext = hintType.preferredFilenameExtension ?? "bin" let mime = hintType.preferredMIMEType ?? "application/octet-stream" write(data: data, ext: ext, mime: mime, name: "") return } completion(nil) } private func mimeFromPath(url: URL) -> String { if let type = UTType(filenameExtension: url.pathExtension), let mime = type.preferredMIMEType { return mime } return "application/octet-stream" } private func writeManifest(files: [[String: String]]) { guard let container = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: appGroupId ) else { return } let manifest: [String: Any] = [ "createdAt": Date().timeIntervalSince1970, "files": files, ] let path = container.appendingPathComponent("shared-inbox").appendingPathComponent("manifest.json") if let data = try? JSONSerialization.data(withJSONObject: manifest, options: []) { try? data.write(to: path) } } private func openHostApp() { guard let url = URL(string: "anydrop://share") else { return } // Walk up responder chain to find something that can open URLs. var responder: UIResponder? = self while let r = responder { if let application = r as? UIApplication { application.open(url, options: [:], completionHandler: nil) return } if let selector = NSSelectorFromString("openURL:") as Selector?, r.responds(to: selector) { _ = r.perform(selector, with: url) return } responder = r.next } } private func finish() { extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } }