92 lines
3.7 KiB
Swift
92 lines
3.7 KiB
Swift
// 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)
|
|
}
|
|
}
|