Files
Commilitia-Drop/ios/CDrop/Share/ShareViewController.swift
T

175 lines
7.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import UIKit
import UniformTypeIdentifiers
// Share Extension share sheet App Group ****
// ShareGuideView + + + Commilitia Drop app
// 120MB jetsam PLAN §I5
//
// iOS share 宿 app
// openURL: iOS 10 iOS 18+
// IMP open App Group app
// CDropApp.scenePhase(.active)loadPendingShares
final class ShareViewController: UIViewController
{
private let model = ShareModel()
private var started = false
// URL
private var stagedURLs: [URL] = []
override func viewDidLoad()
{
super.viewDidLoad()
let host = UIHostingController(
rootView: ShareGuideView(
model: model,
onOpen: { [weak self] in self?.openAndFinish() },
onCancel: { [weak self] in self?.cancelAndFinish() }))
addChild(host)
host.view.frame = view.bounds
host.view.autoresizingMask = [ .flexibleWidth, .flexibleHeight ]
view.addSubview(host.view)
host.didMove(toParent: self)
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
guard !started else { return } // viewDidAppear
started = true
Task { await stageAll() }
}
private func stageAll() async
{
let items = (extensionContext?.inputItems as? [NSExtensionItem]) ?? []
var urls: [URL] = []
for item in items
{
for provider in item.attachments ?? []
{
if let dest = await stage(provider) { urls.append(dest) }
}
}
stagedURLs = urls
await MainActor.run { model.fileCount = urls.count }
}
// Commilitia Drop app completeRequest
// completeRequest app
private func openAndFinish()
{
if let url = URL(string: "commilitia-drop://share") { openViaResponder(url) }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35)
{ [weak self] in
self?.extensionContext?.completeRequest(returningItems: nil)
}
}
// app app
private func cancelAndFinish()
{
for url in stagedURLs
{
try? FileManager.default.removeItem(at: url.deletingLastPathComponent())
}
extensionContext?.completeRequest(returningItems: nil)
}
// App Group URL退
// nil
private func stage(_ provider: NSItemProvider) async -> URL?
{
if let u = await stageFile(provider) { return u }
return await stageData(provider)
}
private func stageFile(_ provider: NSItemProvider) async -> URL?
{
let typeId = provider.registeredTypeIdentifiers.first ?? UTType.item.identifier
return await withCheckedContinuation
{ cont in
provider.loadFileRepresentation(forTypeIdentifier: typeId)
{ url, _ in
guard let url, let dest = AppGroup.stagedDestination(originalName: url.lastPathComponent)
else { cont.resume(returning: nil); return }
do
{
try FileManager.default.copyItem(at: url, to: dest)
cont.resume(returning: dest)
}
catch
{
cont.resume(returning: nil)
}
}
}
}
private func stageData(_ provider: NSItemProvider) async -> URL?
{
let typeId = provider.registeredTypeIdentifiers.first ?? UTType.data.identifier
let name = suggestedName(provider, typeId: typeId)
return await withCheckedContinuation
{ cont in
provider.loadDataRepresentation(forTypeIdentifier: typeId)
{ data, _ in
guard let data, let dest = AppGroup.stagedDestination(originalName: name)
else { cont.resume(returning: nil); return }
do
{
try data.write(to: dest)
cont.resume(returning: dest)
}
catch
{
cont.resume(returning: nil)
}
}
}
}
//
private func suggestedName(_ provider: NSItemProvider, typeId: String) -> String
{
if let s = provider.suggestedName, !s.isEmpty
{
if (s as NSString).pathExtension.isEmpty,
let ext = UTType(typeId)?.preferredFilenameExtension
{
return "\(s).\(ext)"
}
return s
}
let ext = UTType(typeId)?.preferredFilenameExtension ?? "dat"
return "shared-\(UUID().uuidString.prefix(8)).\(ext)"
}
// 沿 UIApplication IMP open(_:options:completionHandler:)
// UIApplication 退 openURL:iOS 18+
private func openViaResponder(_ url: URL)
{
let modern = NSSelectorFromString("openURL:options:completionHandler:")
var responder: UIResponder? = self
while let r = responder
{
if r.responds(to: modern), let app = r as? NSObject,
let method = class_getInstanceMethod(type(of: app), modern)
{
typealias OpenFn = @convention(c) (NSObject, Selector, NSURL, NSDictionary, Any?) -> Void
let fn = unsafeBitCast(method_getImplementation(method), to: OpenFn.self)
fn(app, modern, url as NSURL, NSDictionary(), nil)
return
}
responder = r.next
}
let legacy = NSSelectorFromString("openURL:")
responder = self
while let r = responder
{
if r.responds(to: legacy) { _ = r.perform(legacy, with: url); return }
responder = r.next
}
}
}