iOS: How to let user share URL from Safari/Chrome in SwiftUI App

Viewed 205

I would like to add a feature in my app, where a user browsing the web can press "Share" on the link, and when the normal apps come up, like "Messages", "Mail", "Instagram" etc., they can see my app, and when they tap it, I want to handle the url inside my app.

I've tried searching: "swiftui add link from safari to app" but I can only find handling deepLinks with onOpenURL

I'm assuming from the experience I get when I share a link to one of these apps, I have to build an extension for the SwiftUI app?

I was wondering if there are any solutions, or if I'm searching for this problem using the wrong terms.

Any help would be greatly appreciated, thank you!

2 Answers

There is a way to accomplish that using the Share Extension.

But, I would like to mention that the share extensions' intended approach is handling all of the necessary work itself. The provided solution is a workaround.

Steps

  1. Configure your app to handle Deep Linking. Here is a good article that explains how to accomplish that in SwiftUI.
  2. Create a new Share Extension (File -> New -> Target -> Share Extension). Proceed with the default settings.
  3. Replace the content of the newly created Info.plist (in the extension target) with this
<?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>NSExtension</key>
    <dict>
        <key>NSExtensionActionWantsFullScreenPresentation</key>
        <true/>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                <integer>1</integer>
            </dict>
        </dict>
        <key>NSExtensionPrincipalClass</key>
        <string>ShareNavigationController</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.share-services</string>
    </dict>
</dict>
</plist>
  1. Replace the content of the newly created View Controller file (in the extension target) with the following code:
import UIKit
import Social
import UniformTypeIdentifiers
import SwiftUI

@objc(ShareNavigationController)
class ShareNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        retrieveSharedURL { [weak self] url in
            guard let url = url else {
                let error = NSError(domain: "your_bundle_id", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to retrieve the url"])
                self?.extensionContext?.cancelRequest(withError: error)
                return
            }

            guard var components = URLComponents(string: "your_custom_scheme://share_extension") else {
                let error = NSError(domain: "your_bundle_id", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to create the components"])
                self?.extensionContext?.cancelRequest(withError: error)
                return

            }
            
            components.queryItems = [URLQueryItem(name: "share_url", value: url.absoluteString)]
            guard let deepLinkURL = components.url else {
                let error = NSError(domain: "your_bundle_id", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to create the deep-link url"])
                self?.extensionContext?.cancelRequest(withError: error)
                return

            }
            
            _ = self?.openURL(deepLinkURL)
            self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
        }
    }
    
    func retrieveSharedURL(_ completion: @escaping (URL?) -> ()) {
        let attachment = (extensionContext?.inputItems as? [NSExtensionItem])?
            .reduce([], { $0 + ($1.attachments ?? []) })
            .first(where: { $0.hasItemConformingToTypeIdentifier(UTType.url.identifier) })
        
        if let attachment = attachment {
            attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { (item, _) in
                completion(item as? URL)
            }
        }
    }
    
    @objc func openURL(_ url: URL) -> Bool {
        var responder: UIResponder? = self
        while responder != nil {
            if let application = responder as? UIApplication {
                return application.perform(#selector(openURL(_:)), with: url) != nil
            }
            responder = responder?.next
        }
        return false
    }
}

  1. Delete the MainInterface.storyboard file.
  2. Retrieve the share URL from the deep-link in your host app:
// ...

 guard let components = URLComponents(url: deepLinkURL, resolvingAgainstBaseURL: true),
       let queryItem = components.queryItems?.first(where: { $0.name == "share_url" })?.value,
       let shareURL = URL(string: queryItem) else { return }
    
// Handle the url
print(shareURL)

What you want is to make your app appear on UIActivityViewController

enter image description here

add the flowing to info.plist

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>NSExtensionActivationRule</key> 
        <dict>
            <key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
            <integer>1</integer> 
            <key>NSExtensionActivationSupportsText</key>
            <true/>
            <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
            <integer>1</integer>
        </dict>
        <key>NSExtensionJavaScriptPreprocessingFile</key>
        <string>testshare</string>
    </dict> 
    <key>NSExtensionMainStoryboard</key>
    <string>MainInterface</string>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.share-services</string>
</dict> 

also check this answer

Related