import UIKit import MobileCoreServices import UniformTypeIdentifiers public class SharedMediaFile: Codable { var path: String var mimeType: String? var thumbnail: String? // video thumbnail var duration: Double? // video duration in milliseconds var message: String? // post message var type: SharedMediaType public init( path: String, mimeType: String? = nil, thumbnail: String? = nil, duration: Double? = nil, message: String?=nil, type: SharedMediaType) { self.path = path self.mimeType = mimeType self.thumbnail = thumbnail self.duration = duration self.message = message self.type = type } } public enum SharedMediaType: String, Codable, CaseIterable { case image case video case text case file case url public var toUTTypeIdentifier: String { if #available(iOS 14.0, *) { switch self { case .image: return UTType.image.identifier case .video: return UTType.movie.identifier case .text: return UTType.text.identifier case .file: return UTType.fileURL.identifier case .url: return UTType.url.identifier } } switch self { case .image: return "public.image" case .video: return "public.movie" case .text: return "public.text" case .file: return "public.file-url" case .url: return "public.url" } } } let kSchemePrefix = "ShareMedia" let kUserDefaultsKey = "ShareKey" let kUserDefaultsMessageKey = "ShareMessageKey" let kAppGroupIdKey = "AppGroupId" class ActionViewController: UIViewController { var hostAppBundleIdentifier = "com.jiaqi.hacki" var appGroupId = "group.com.jiaqi.hacki" var sharedText: [String] = [] var sharedMedia: [SharedMediaFile] = [] let urlContentType = UTType.url @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let content = extensionContext!.inputItems[0] as? NSExtensionItem { if let contents = content.attachments { for (index, attachment) in (contents).enumerated() { handleUrl(content: content, attachment: attachment, index: index) } } } } private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) { attachment.loadItem(forTypeIdentifier: urlContentType.identifier, options: nil) { [weak self] data, error in if error == nil, let item = data as? URL, let this = self { this.sharedText.append(item.absoluteString) // If this is the last item, save imagesData in userDefaults and redirect to host app if index == (content.attachments?.count)! - 1 { this.sharedMedia.removeAll() this.sharedMedia.append(.init(path: item.absoluteString, type: .url)) print(this.sharedText) this.saveAndRedirect() } } else { self?.dismissWithError() } } } private func dismissWithError() { print("[ERROR] Error loading data!") let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert) let action = UIAlertAction(title: "Error", style: .cancel) { _ in self.dismiss(animated: true, completion: nil) } alert.addAction(action) present(alert, animated: true, completion: nil) extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } // Save shared media and redirect to host app private func saveAndRedirect(message: String? = nil) { let userDefaults = UserDefaults(suiteName: appGroupId) userDefaults?.set(toData(data: sharedMedia), forKey: kUserDefaultsKey) userDefaults?.set(message, forKey: kUserDefaultsMessageKey) userDefaults?.synchronize() redirectToHostApp() } private func toData(data: [SharedMediaFile]) -> Data { let encodedData = try? JSONEncoder().encode(data) return encodedData! } private func redirectToHostApp() { // ids may not loaded yet so we need loadIds here too loadIds() let url = URL(string: "\(kSchemePrefix)-\(hostAppBundleIdentifier):share") var responder = self as UIResponder? if #available(iOS 18.0, *) { while responder != nil { if let application = responder as? UIApplication { application.open(url!, options: [:], completionHandler: nil) } responder = responder?.next } } else { let selectorOpenURL = sel_registerName("openURL:") while (responder != nil) { if (responder?.responds(to: selectorOpenURL))! { _ = responder?.perform(selectorOpenURL, with: url) } responder = responder!.next } } extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } private func loadIds() { // loading Share extension App Id let shareExtensionAppBundleIdentifier = Bundle.main.bundleIdentifier! // extract host app bundle id from ShareExtension id // by default it's . // for example: "com.kasem.sharing.Share-Extension" -> com.kasem.sharing let lastIndexOfPoint = shareExtensionAppBundleIdentifier.lastIndex(of: ".") hostAppBundleIdentifier = String(shareExtensionAppBundleIdentifier[.. let customAppGroupId = Bundle.main.object(forInfoDictionaryKey: kAppGroupIdKey) as? String appGroupId = customAppGroupId ?? defaultAppGroupId } @IBAction func done() { // Return any edited content to the host app. // This template doesn't do anything, so we just echo the passed in items. self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil) } }