You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:49:51