- Refactor ShareViewController to extract URL and open main app instead of direct API calls - Add robust URL extraction from multiple content types (URL, text, property lists) - Implement comprehensive debugging for Share Extension content processing - Add URL scheme handling in main app (readeck://add-bookmark) - Add notification-based communication between Share Extension and main app - Extend BookmarksViewModel with share notification handling - Support automatic AddBookmarkView opening with prefilled URL and title from shares Technical changes: - Remove Core Data dependency from Share Extension - Add extensionContext.open() for launching main app with custom URL scheme - Process all registered type identifiers for robust content extraction - Add NSDataDetector for URL extraction from plain text - Handle Safari property list sharing format - Add share state management in BookmarksViewModel (@Observable pattern) - Implement NotificationCenter publisher pattern with Combine URL scheme format: readeck://add-bookmark?url=...&title=... Notification: 'AddBookmarkFromShare' with url and title in userInfo
224 lines
7.7 KiB
Swift
224 lines
7.7 KiB
Swift
//
|
|
// ShareViewController.swift
|
|
// URLShare
|
|
//
|
|
// Created by Ilyas Hallak on 11.06.25.
|
|
//
|
|
|
|
import UIKit
|
|
import Social
|
|
import UniformTypeIdentifiers
|
|
|
|
class ShareViewController: SLComposeServiceViewController {
|
|
|
|
private var extractedURL: String?
|
|
private var extractedTitle: String?
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
extractSharedContent()
|
|
|
|
// Automatisch die Haupt-App öffnen, sobald URL extrahiert wurde
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
self.openParentApp()
|
|
}
|
|
}
|
|
|
|
override func isContentValid() -> Bool {
|
|
return true // Immer true, damit der Button funktioniert
|
|
}
|
|
|
|
override func didSelectPost() {
|
|
openMainApp()
|
|
}
|
|
|
|
// MARK: - Private Methods
|
|
|
|
private func extractSharedContent() {
|
|
guard let extensionContext = extensionContext else { return }
|
|
|
|
print("=== DEBUG: Starting content extraction ===")
|
|
print("Input items count: \(extensionContext.inputItems.count)")
|
|
|
|
for (itemIndex, item) in extensionContext.inputItems.enumerated() {
|
|
guard let inputItem = item as? NSExtensionItem else { continue }
|
|
|
|
print("Item \(itemIndex) - attachments: \(inputItem.attachments?.count ?? 0)")
|
|
|
|
// Versuche alle verfügbaren Type Identifiers
|
|
for (attachmentIndex, provider) in (inputItem.attachments ?? []).enumerated() {
|
|
print("Attachment \(attachmentIndex) - registered types: \(provider.registeredTypeIdentifiers)")
|
|
|
|
// Iteriere durch alle registrierten Type Identifiers
|
|
for typeIdentifier in provider.registeredTypeIdentifiers {
|
|
print("Trying type identifier: \(typeIdentifier)")
|
|
|
|
provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { [weak self] item, error in
|
|
if let error = error {
|
|
print("Error loading \(typeIdentifier): \(error)")
|
|
return
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
self?.processLoadedItem(item, typeIdentifier: typeIdentifier, inputItem: inputItem)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func processLoadedItem(_ item: Any?, typeIdentifier: String, inputItem: NSExtensionItem) {
|
|
print("Processing item of type \(typeIdentifier): \(type(of: item))")
|
|
|
|
// URL direkt
|
|
if let url = item as? URL {
|
|
print("Found URL: \(url.absoluteString)")
|
|
extractedURL = url.absoluteString
|
|
extractedTitle = inputItem.attributedTitle?.string ?? inputItem.attributedContentText?.string
|
|
return
|
|
}
|
|
|
|
// NSURL
|
|
if let nsurl = item as? NSURL {
|
|
print("Found NSURL: \(nsurl.absoluteString ?? "nil")")
|
|
extractedURL = nsurl.absoluteString
|
|
extractedTitle = inputItem.attributedTitle?.string ?? inputItem.attributedContentText?.string
|
|
return
|
|
}
|
|
|
|
// String (könnte URL sein)
|
|
if let text = item as? String {
|
|
print("Found String: \(text)")
|
|
if URL(string: text) != nil {
|
|
extractedURL = text
|
|
extractedTitle = inputItem.attributedTitle?.string ?? inputItem.attributedContentText?.string
|
|
return
|
|
}
|
|
|
|
// Versuche URL aus Text zu extrahieren
|
|
if let extractedURL = extractURLFromText(text) {
|
|
self.extractedURL = extractedURL
|
|
self.extractedTitle = text != extractedURL ? text : nil
|
|
return
|
|
}
|
|
}
|
|
|
|
// Dictionary (Property List)
|
|
if let dictionary = item as? [String: Any] {
|
|
print("Found Dictionary: \(dictionary)")
|
|
handlePropertyList(dictionary)
|
|
return
|
|
}
|
|
|
|
// NSData - versuche als String zu interpretieren
|
|
if let data = item as? Data {
|
|
if let text = String(data: data, encoding: .utf8) {
|
|
print("Found Data as String: \(text)")
|
|
if URL(string: text) != nil {
|
|
extractedURL = text
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
print("Could not process item of type: \(type(of: item))")
|
|
}
|
|
|
|
private func extractURLFromText(_ text: String) -> String? {
|
|
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
|
let range = NSRange(text.startIndex..<text.endIndex, in: text)
|
|
|
|
if let match = detector?.firstMatch(in: text, options: [], range: range),
|
|
let url = match.url {
|
|
return url.absoluteString
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
private func handlePropertyList(_ dictionary: [String: Any]) {
|
|
// Safari und andere Browser verwenden oft Property Lists
|
|
if let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any] {
|
|
if let url = results["URL"] as? String {
|
|
extractedURL = url
|
|
extractedTitle = results["title"] as? String
|
|
return
|
|
}
|
|
}
|
|
|
|
// Direkte URL im Dictionary
|
|
if let url = dictionary["URL"] as? String {
|
|
extractedURL = url
|
|
extractedTitle = dictionary["title"] as? String
|
|
return
|
|
}
|
|
|
|
// Andere mögliche Keys
|
|
for key in dictionary.keys {
|
|
if let value = dictionary[key] as? String, URL(string: value) != nil {
|
|
extractedURL = value
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
private func openMainApp() {
|
|
let url = extractedURL ?? "https://example.com"
|
|
let title = extractedTitle ?? ""
|
|
|
|
print("Opening main app with URL: \(url)")
|
|
|
|
// Verwende NSUserActivity anstatt URL-Schema
|
|
let userActivity = NSUserActivity(activityType: "de.ilyas.readeck")
|
|
userActivity.userInfo = [
|
|
"url": url,
|
|
"title": title
|
|
]
|
|
userActivity.webpageURL = URL(string: url)
|
|
|
|
// Extension schließen und Activity übergeben
|
|
extensionContext?.completeRequest(returningItems: [userActivity], completionHandler: nil)
|
|
}
|
|
|
|
private func completeRequest() {
|
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
}
|
|
|
|
func openParentApp() {
|
|
guard let extensionContext = extensionContext else { return }
|
|
|
|
let url = extractedURL ?? "https://example.com"
|
|
let title = extractedTitle ?? ""
|
|
|
|
print("Opening parent app with URL: \(url)")
|
|
|
|
// URL für die Haupt-App erstellen mit Parametern
|
|
var urlComponents = URLComponents(string: "readeck://add-bookmark")
|
|
urlComponents?.queryItems = [
|
|
URLQueryItem(name: "url", value: url)
|
|
]
|
|
|
|
if !title.isEmpty {
|
|
urlComponents?.queryItems?.append(URLQueryItem(name: "title", value: title))
|
|
}
|
|
|
|
guard let finalURL = urlComponents?.url else {
|
|
print("Failed to create final URL")
|
|
return
|
|
}
|
|
|
|
print("Final URL: \(finalURL)")
|
|
|
|
var responder: UIResponder? = self
|
|
|
|
while responder != nil {
|
|
if let application = responder as? UIApplication {
|
|
application.open(finalURL)
|
|
break
|
|
}
|
|
responder = responder?.next
|
|
}
|
|
}
|
|
}
|