comment_list.inc': $pre .= $default_pre .= 'comment_list.inc.htm'; break; case 'message': $pre .= $default_pre .= 'message.htm'; break; case 'tag_list': $pre .= $default_pre .= 'tag_list.htm'; break; case 'tag': $pre .= $default_pre .= 'tag.htm'; break; case 'flag': $pre .= $default_pre .= 'flag.htm'; break; case 'my': $pre .= $default_pre .= 'my.htm'; break; case 'my_password': $pre .= $default_pre .= 'my_password.htm'; break; case 'my_bind': $pre .= $default_pre .= 'my_bind.htm'; break; case 'my_avatar': $pre .= $default_pre .= 'my_avatar.htm'; break; case 'home_article': $pre .= $default_pre .= 'home_article.htm'; break; case 'home_comment': $pre .= $default_pre .= 'home_comment.htm'; break; case 'user': $pre .= $default_pre .= 'user.htm'; break; case 'user_login': $pre .= $default_pre .= 'user_login.htm'; break; case 'user_create': $pre .= $default_pre .= 'user_create.htm'; break; case 'user_resetpw': $pre .= $default_pre .= 'user_resetpw.htm'; break; case 'user_resetpw_complete': $pre .= $default_pre .= 'user_resetpw_complete.htm'; break; case 'user_comment': $pre .= $default_pre .= 'user_comment.htm'; break; case 'single_page': $pre .= $default_pre .= 'single_page.htm'; break; case 'search': $pre .= $default_pre .= 'search.htm'; break; case 'operate_sticky': $pre .= $default_pre .= 'operate_sticky.htm'; break; case 'operate_close': $pre .= $default_pre .= 'operate_close.htm'; break; case 'operate_delete': $pre .= $default_pre .= 'operate_delete.htm'; break; case 'operate_move': $pre .= $default_pre .= 'operate_move.htm'; break; case '404': $pre .= $default_pre .= '404.htm'; break; case 'read_404': $pre .= $default_pre .= 'read_404.htm'; break; case 'list_404': $pre .= $default_pre .= 'list_404.htm'; break; default: $pre .= $default_pre .= theme_mode_pre(); break; } if ($config['theme']) { $conffile = APP_PATH . 'view/template/' . $config['theme'] . '/conf.json'; $json = is_file($conffile) ? xn_json_decode(file_get_contents($conffile)) : array(); } !empty($json['installed']) and $path_file = APP_PATH . 'view/template/' . $config['theme'] . '/htm/' . ($id ? $id . '_' : '') . $pre; (empty($path_file) || !is_file($path_file)) and $path_file = APP_PATH . 'view/template/' . $config['theme'] . '/htm/' . $pre; if (!empty($config['theme_child']) && is_array($config['theme_child'])) { foreach ($config['theme_child'] as $theme) { if (empty($theme) || is_array($theme)) continue; $path_file = APP_PATH . 'view/template/' . $theme . '/htm/' . ($id ? $id . '_' : '') . $pre; !is_file($path_file) and $path_file = APP_PATH . 'view/template/' . $theme . '/htm/' . $pre; } } !is_file($path_file) and $path_file = APP_PATH . ($dir ? 'plugin/' . $dir . '/view/htm/' : 'view/htm/') . $default_pre; return $path_file; } function theme_mode_pre($type = 0) { global $config; $mode = $config['setting']['website_mode']; $pre = ''; if (1 == $mode) { $pre .= 2 == $type ? 'portal_category.htm' : 'portal.htm'; } elseif (2 == $mode) { $pre .= 2 == $type ? 'flat_category.htm' : 'flat.htm'; } else { $pre .= 2 == $type ? 'index_category.htm' : 'index.htm'; } return $pre; } ?>swift - How to pass a url to iOS app from share sheet? - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - How to pass a url to iOS app from share sheet? - Stack Overflow

programmeradmin0浏览0评论

UPDATE: It appears that this behavior is not allowed in iOS, despite popular apps like ChatGPT being allowed to do it... To bypass this, I now just pass the URL over to the app when the app icon is clicked from the share sheet, and then I send a notification to the user that tells them to click it to continue. I think this is legal behavior under Apple's dev guidelines, but who knows anymore.

====================================================================

I've been trying to implement a simple feature: when a user shares a webpage via iOS's share sheet, clicking my app's Action Extension should launch the app and pass along the current URL for display. I set up a custom URL scheme ("myapp") in my URL Types. For example, when I type myapp://share?url=example in Safari, I get a prompt to open my app, and after I confirm, the URL is correctly passed and displayed in my app.

However, everytime I click the Action Extension, opening the app via the deeplink fails. For example, here is what is printed:

Action Extension: Deep Link formed: myapp://share?url=www.apple

Action Extension: Failed to open main app

Main App

import SwiftUI
import SwiftData

// You can keep NavigationManager here or move it to its own file.
class NavigationManager: ObservableObject {
    static let shared = NavigationManager()
    @Published var sharedURL: URL? = nil
    
    func navigateToURL(_ url: URL) {
        sharedURL = url
    }
}

// The AppDelegate that handles deep linking
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     open url: URL,
                     options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        print("AppDelegate triggered with URL: \(url)")
        // (Your URL parsing and handling code follows here)
        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 {
    // Connect your AppDelegate to the SwiftUI lifecycle.
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @StateObject private var navManager = NavigationManager.shared
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(navManager)
                .onOpenURL { url in
                    // Optional fallback if needed; the AppDelegate should already handle it.
                    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)
    }
}

Action View Controller

import UIKit
import UniformTypeIdentifiers

class ActionViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Optionally hide the view if you want no visible UI
        view.isHidden = true
        processInput()
    }
    
    private func processInput() {
        print("Action Extension: processInput started")
        
        // Get the first input item
        guard let inputItem = extensionContext?.inputItems.first as? NSExtensionItem else {
            completeRequest()
            return
        }
        
        // Try to extract a URL from the item
        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")
                    selfpleteRequest()
                }
            }
        } else {
            print("Action Extension: No valid attachment found")
            completeRequest()
        }
    }
    
    private func handleURL(_ url: URL) {
        // Encode the URL to form a proper deep link
        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)")
        
        // Attempt to open the main app with the deep link
        extensionContext?.open(deepLink) { success in
            if success {
                print("Action Extension: Opened main app successfully")
            } else {
                print("Action Extension: Failed to open main app")
            }
            selfpleteRequest()
        }
    }
    
    private func completeRequest() {
        // Dismiss the extension
        extensionContext?pleteRequest(returningItems: nil, completionHandler: nil)
    }
}

Action Extension plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ".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>


Action Request Handler

import UIKit
import MobileCoreServices
import UniformTypeIdentifiers

class ActionRequestHandler: NSObject, NSExtensionRequestHandling {

    var extensionContext: NSExtensionContext?
    
    func beginRequest(with context: NSExtensionContext) {
        // Do not call super in an Action extension with no user interface
        self.extensionContext = context
        
        var found = false
        
        // Find the item containing the results from the JavaScript preprocessing.
        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]) {
        // Here, do something, potentially asynchronously, with the preprocessing
        // results.
        
        // In this very simple example, the JavaScript will have passed us the
        // current background color style, if there is one. We will construct a
        // dictionary to send back with a desired new background color style.
        let bgColor: Any? = javaScriptPreprocessingResults["currentBackgroundColor"]
        if bgColor == nil ||  bgColor! as! String == "" {
            // No specific background color? Request setting the background to red.
            self.doneWithResults(["newBackgroundColor": "red"])
        } else {
            // Specific background color is set? Request replacing it with green.
            self.doneWithResults(["newBackgroundColor": "green"])
        }
    }
    
    func doneWithResults(_ resultsForJavaScriptFinalizeArg: [String: Any]?) {
        if let resultsForJavaScriptFinalize = resultsForJavaScriptFinalizeArg {
            // Construct an NSExtensionItem of the appropriate type to return our
            // results dictionary in.
            
            // These will be used as the arguments to the JavaScript finalize()
            // method.
            
            let resultsDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: resultsForJavaScriptFinalize]
            
            let resultsProvider = NSItemProvider(item: resultsDictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier)
            
            let resultsItem = NSExtensionItem()
            resultsItem.attachments = [resultsProvider]
            
            // Signal that we're complete, returning our results.
            self.extensionContext!pleteRequest(returningItems: [resultsItem], completionHandler: nil)
        } else {
            // We still need to signal that we're done even if we have nothing to
            // pass back.
            self.extensionContext!pleteRequest(returningItems: [], completionHandler: nil)
        }
        
        // Don't hold on to this after we finished with it.
        self.extensionContext = nil
    }

}

Main App plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ".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>

发布评论

评论列表(0)

  1. 暂无评论