195 lines
5.3 KiB
Swift
195 lines
5.3 KiB
Swift
import MobileCoreServices
|
|
import UniformTypeIdentifiers
|
|
import UIKit
|
|
|
|
private let schemePrefix = "ShareMedia"
|
|
private let userDefaultsKey = "ShareKey"
|
|
private let userDefaultsMessageKey = "ShareMessageKey"
|
|
private let appGroupIdKey = "AppGroupId"
|
|
|
|
private struct SharedMediaFile: Codable {
|
|
let path: String
|
|
let mimeType: String?
|
|
let thumbnail: String?
|
|
let duration: Double?
|
|
let message: String?
|
|
let type: SharedMediaType
|
|
}
|
|
|
|
private enum SharedMediaType: String, Codable {
|
|
case text
|
|
case url
|
|
}
|
|
|
|
final class SaveActionViewController: UIViewController {
|
|
private var hostAppBundleIdentifier = ""
|
|
private var appGroupId = ""
|
|
private var sharedMedia: [SharedMediaFile] = []
|
|
private let group = DispatchGroup()
|
|
private var didStart = false
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
guard !didStart else {
|
|
return
|
|
}
|
|
didStart = true
|
|
loadIdentifiers()
|
|
loadSharedContent()
|
|
}
|
|
|
|
private func loadIdentifiers() {
|
|
let actionExtensionIdentifier = Bundle.main.bundleIdentifier ?? ""
|
|
if let lastDotIndex = actionExtensionIdentifier.lastIndex(of: ".") {
|
|
hostAppBundleIdentifier = String(actionExtensionIdentifier[..<lastDotIndex])
|
|
} else {
|
|
hostAppBundleIdentifier = actionExtensionIdentifier
|
|
}
|
|
|
|
let defaultAppGroupId = "group.\(hostAppBundleIdentifier)"
|
|
appGroupId =
|
|
Bundle.main.object(forInfoDictionaryKey: appGroupIdKey) as? String
|
|
?? defaultAppGroupId
|
|
}
|
|
|
|
private func loadSharedContent() {
|
|
guard let inputItems = extensionContext?.inputItems as? [NSExtensionItem] else {
|
|
finish()
|
|
return
|
|
}
|
|
|
|
for item in inputItems {
|
|
appendAttributedText(item.attributedContentText)
|
|
for provider in item.attachments ?? [] {
|
|
load(provider)
|
|
}
|
|
}
|
|
|
|
group.notify(queue: .main) { [weak self] in
|
|
self?.finish()
|
|
}
|
|
}
|
|
|
|
private func load(_ provider: NSItemProvider) {
|
|
if provider.hasItemConformingToTypeIdentifier(urlTypeIdentifier) {
|
|
group.enter()
|
|
provider.loadItem(forTypeIdentifier: urlTypeIdentifier, options: nil) { [weak self] item, _ in
|
|
defer {
|
|
self?.group.leave()
|
|
}
|
|
if let url = item as? URL {
|
|
self?.appendLiteral(url.absoluteString, type: .url, mimeType: nil)
|
|
} else if let text = item as? String {
|
|
self?.appendLiteral(text, type: .url, mimeType: nil)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
if provider.hasItemConformingToTypeIdentifier(textTypeIdentifier) {
|
|
group.enter()
|
|
provider.loadItem(forTypeIdentifier: textTypeIdentifier, options: nil) { [weak self] item, _ in
|
|
defer {
|
|
self?.group.leave()
|
|
}
|
|
if let text = item as? String {
|
|
self?.appendLiteral(text, type: .text, mimeType: "text/plain")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var textTypeIdentifier: String {
|
|
if #available(iOS 14.0, *) {
|
|
return UTType.text.identifier
|
|
}
|
|
return kUTTypeText as String
|
|
}
|
|
|
|
private var urlTypeIdentifier: String {
|
|
if #available(iOS 14.0, *) {
|
|
return UTType.url.identifier
|
|
}
|
|
return kUTTypeURL as String
|
|
}
|
|
|
|
private func appendAttributedText(_ text: NSAttributedString?) {
|
|
guard let value = text?.string else {
|
|
return
|
|
}
|
|
appendLiteral(value, type: .text, mimeType: "text/plain")
|
|
}
|
|
|
|
private func appendLiteral(_ value: String, type: SharedMediaType, mimeType: String?) {
|
|
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else {
|
|
return
|
|
}
|
|
guard !sharedMedia.contains(where: { $0.type == type && $0.path == trimmed }) else {
|
|
return
|
|
}
|
|
|
|
sharedMedia.append(
|
|
SharedMediaFile(
|
|
path: trimmed,
|
|
mimeType: mimeType,
|
|
thumbnail: nil,
|
|
duration: nil,
|
|
message: nil,
|
|
type: type
|
|
)
|
|
)
|
|
}
|
|
|
|
private func finish() {
|
|
guard !sharedMedia.isEmpty else {
|
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
return
|
|
}
|
|
saveAndRedirect()
|
|
}
|
|
|
|
private func saveAndRedirect() {
|
|
guard let encodedData = try? JSONEncoder().encode(sharedMedia) else {
|
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
return
|
|
}
|
|
|
|
let userDefaults = UserDefaults(suiteName: appGroupId)
|
|
userDefaults?.set(encodedData, forKey: userDefaultsKey)
|
|
userDefaults?.set(nil, forKey: userDefaultsMessageKey)
|
|
userDefaults?.synchronize()
|
|
redirectToHostApp()
|
|
}
|
|
|
|
private func redirectToHostApp() {
|
|
guard let url = URL(string: "\(schemePrefix)-\(hostAppBundleIdentifier):share") else {
|
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
return
|
|
}
|
|
|
|
var responder: UIResponder? = self
|
|
let selectorOpenUrl = sel_registerName("openURL:")
|
|
|
|
if #available(iOS 18.0, *) {
|
|
while responder != nil {
|
|
if let application = responder as? UIApplication {
|
|
application.open(url, options: [:], completionHandler: nil)
|
|
break
|
|
}
|
|
responder = responder?.next
|
|
}
|
|
} else {
|
|
while responder != nil {
|
|
if responder?.responds(to: selectorOpenUrl) == true {
|
|
_ = responder?.perform(selectorOpenUrl, with: url)
|
|
break
|
|
}
|
|
responder = responder?.next
|
|
}
|
|
}
|
|
|
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
}
|
|
}
|