iOS分享表单向App传递URL失败问题排查与替代方案
更新说明: 尽管ChatGPT等热门应用可实现该功能,但iOS似乎不允许此类行为...为绕过限制,我改为在用户点击分享表单中的App图标时传递URL,再发送通知提示用户点击继续。我认为这符合Apple开发者指南,但无法完全确定。
我尝试实现一个简单功能:用户通过iOS分享表单分享网页时,点击我的应用动作扩展(Action Extension)应启动App并传递当前URL以供展示。我在URL Types中配置了自定义URL方案("myapp"),例如在Safari中输入myapp://share?url=example.com时,会弹出打开应用的提示,确认后URL可正确传递并在App中展示。
但每次点击动作扩展时,通过深度链接打开App均失败,打印信息如下:
Action Extension: Deep Link formed: myapp://share?url=www.apple.com
Action Extension: Failed to open main app
主应用代码
import SwiftUI import SwiftData // 你可以将NavigationManager放在这里,也可以移到单独文件中。 class NavigationManager: ObservableObject { static let shared = NavigationManager() @Published var sharedURL: URL? = nil func navigateToURL(_ url: URL) { sharedURL = url } } // 处理深度链接的AppDelegate import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { print("AppDelegate triggered with URL: \(url)") // (URL解析和处理代码如下) guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), components.scheme == "myapp", components.host == "share", let queryItem = components.queryItems?.first(where: { $0.name == "url" }), let sharedURLString = queryItem.value, let sharedURL = URL(string: sharedURLString) else { return false } NavigationManager.shared.navigateToURL(sharedURL) return true } } @main struct MyApp: App { // 将AppDelegate连接到SwiftUI生命周期。 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject private var navManager = NavigationManager.shared var body: some Scene { WindowGroup { ContentView() .environmentObject(navManager) .onOpenURL { url in // 可选的回退处理;AppDelegate应该已处理该逻辑。 handleSharedURL(url) } } } private func handleSharedURL(_ url: URL) { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), components.scheme == "myapp", components.host == "share", let queryItem = components.queryItems?.first(where: { $0.name == "url" }), let sharedURLString = queryItem.value, let sharedURL = URL(string: sharedURLString) else { return } navManager.navigateToURL(sharedURL) } }
动作扩展视图控制器代码
import UIKit import UniformTypeIdentifiers class ActionViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 如果不需要可见UI,可以选择隐藏视图 view.isHidden = true processInput() } private func processInput() { print("Action Extension: processInput started") // 获取第一个输入项 guard let inputItem = extensionContext?.inputItems.first as? NSExtensionItem else { completeRequest() return } // 尝试从项中提取URL let urlType = UTType.url.identifier if let attachment = inputItem.attachments?.first, attachment.hasItemConformingToTypeIdentifier(urlType) { attachment.loadItem(forTypeIdentifier: urlType, options: nil) { [weak self] data, error in guard let self = self else { return } if let url = data as? URL { self.handleURL(url) } else { print("Action Extension: Failed to load URL from attachment") self.completeRequest() } } } else { print("Action Extension: No valid attachment found") completeRequest() } } private func handleURL(_ url: URL) { // 编码URL以生成正确的深度链接 guard let encodedURL = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let deepLink = URL(string: "myapp://share?url=\(encodedURL)") else { print("Action Extension: Error forming deep link") completeRequest() return } print("Action Extension: Deep Link formed: \(deepLink)") // 尝试用深度链接打开主应用 extensionContext?.open(deepLink) { success in if success { print("Action Extension: Opened main app successfully") } else { print("Action Extension: Failed to open main app") } self.completeRequest() } } private func completeRequest() { // 关闭扩展 extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) } }
动作扩展配置文件plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>LSApplicationQueriesSchemes</key> <array> <string>myapp</string> </array> <key>NSExtension</key> <dict> <key>NSExtensionAttributes</key> <dict> <key>NSExtensionActivationRule</key> <string>TRUEPREDICATE</string> </dict> <key>NSExtensionPointIdentifier</key> <string>com.apple.ui-services</string> <key>NSExtensionPrincipalClass</key> <string>$(PRODUCT_MODULE_NAME).ActionViewController</string> </dict> </dict> </plist>
动作扩展请求处理器代码
import UIKit import MobileCoreServices import UniformTypeIdentifiers class ActionRequestHandler: NSObject, NSExtensionRequestHandling { var extensionContext: NSExtensionContext? func beginRequest(with context: NSExtensionContext) { // 在无UI的动作扩展中不要调用super self.extensionContext = context var found = false // 查找包含JavaScript预处理结果的项 outer: for item in context.inputItems as! [NSExtensionItem] { if let attachments = item.attachments { for itemProvider in attachments { if itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) { itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { (item, error) in let dictionary = item as! [String: Any] OperationQueue.main.addOperation { self.itemLoadCompletedWithPreprocessingResults(dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:]) } }) found = true break outer } } } } if !found { self.doneWithResults(nil) } } func itemLoadCompletedWithPreprocessingResults(_ javaScriptPreprocessingResults: [String: Any]) { // 在这里对预处理结果进行处理,可异步执行 // 在这个简单示例中,JavaScript会传递当前背景样式(如果存在)。我们将构造一个字典返回,指定新的背景颜色样式。 let bgColor: Any? = javaScriptPreprocessingResults["currentBackgroundColor"] if bgColor == nil || bgColor! as! String == "" { // 没有指定背景颜色?请求将背景设为红色。 self.doneWithResults(["newBackgroundColor": "red"]) } else { // 已设置指定背景颜色?请求将其替换为绿色。 self.doneWithResults(["newBackgroundColor": "green"]) } } func doneWithResults(_ resultsForJavaScriptFinalizeArg: [String: Any]?) { if let resultsForJavaScriptFinalize = resultsForJavaScriptFinalizeArg { // 构造合适类型的NSExtensionItem以返回结果字典 // 这些将用作JavaScript finalize()方法的参数 let resultsDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: resultsForJavaScriptFinalize] let resultsProvider = NSItemProvider(item: resultsDictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier) let resultsItem = NSExtensionItem() resultsItem.attachments = [resultsProvider] // 标记处理完成,返回结果 self.extensionContext!.completeRequest(returningItems: [resultsItem], completionHandler: nil) } else { // 即使没有结果返回,也要标记处理完成 self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } // 处理完成后释放引用 self.extensionContext = nil } }
主应用配置文件plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>com.elislothower.URLDisplayApp</string> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> </dict> </array> <key>LSApplicationQueriesSchemes</key> <array/> </dict> </plist>
内容的提问来源于stack exchange,提问作者PositiveThirtyThree
相关产品推荐
相关产品推荐

