// iOS Share Extension scaffold. // // Add this as a new "Share Extension" target in gen/apple/ after // `pnpm tauri ios init`. The extension writes the shared payload into the // shared App Group container, then opens the host app via its URL scheme so the // JS layer (lib/native/share.ts -> share_take_pending) can drain it. // // Requirements (Apple Developer): App Group `group.space.ulti.suite` enabled on // BOTH the host app and the extension target. import UIKit import Social import UniformTypeIdentifiers final class ShareViewController: UIViewController { private let appGroup = "group.space.ulti.suite" private let hostScheme = "ultimail" // per app: ultidrive / ultiagenda / ... override func viewDidLoad() { super.viewDidLoad() handleShare() } private func handleShare() { guard let item = extensionContext?.inputItems.first as? NSExtensionItem, let provider = item.attachments?.first else { return complete() } // Text / URL if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) { provider.loadItem(forTypeIdentifier: UTType.url.identifier) { [weak self] data, _ in let url = (data as? URL)?.absoluteString self?.enqueue(kind: "url", text: nil, url: url, files: []) } } else if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) { provider.loadItem(forTypeIdentifier: UTType.plainText.identifier) { [weak self] data, _ in self?.enqueue(kind: "text", text: data as? String, url: nil, files: []) } } else { // File: copy into the shared container, pass its path. provider.loadItem(forTypeIdentifier: UTType.item.identifier) { [weak self] data, _ in guard let self, let src = data as? URL else { self?.complete(); return } let dest = self.copyIntoAppGroup(src) self.enqueue(kind: "file", text: nil, url: nil, files: dest.map { [$0] } ?? []) } } } private func copyIntoAppGroup(_ src: URL) -> String? { guard let container = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? .appendingPathComponent("shared-inbox", isDirectory: true) else { return nil } try? FileManager.default.createDirectory(at: container, withIntermediateDirectories: true) let dest = container.appendingPathComponent(src.lastPathComponent) try? FileManager.default.removeItem(at: dest) try? FileManager.default.copyItem(at: src, to: dest) return dest.path } private func enqueue(kind: String, text: String?, url: String?, files: [String]) { let payload: [String: Any] = [ "kind": kind, "text": text as Any, "url": url as Any, "files": files, ] if let defaults = UserDefaults(suiteName: appGroup) { var queue = defaults.array(forKey: "ulti.share.queue") as? [[String: Any]] ?? [] queue.append(payload) defaults.set(queue, forKey: "ulti.share.queue") } openHostApp() } private func openHostApp() { if let url = URL(string: "\(hostScheme)://go/share") { var responder: UIResponder? = self while let r = responder { if let app = r as? UIApplication { app.open(url, options: [:], completionHandler: nil) break } responder = r.next } } complete() } private func complete() { extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } }