4

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!

Heron. F
  • 232
  • 3
  • 13
  • 1
    First, your should add `onOpenURL` to your SwiftUI app, like described in https://developer.apple.com/forums/thread/651234. Second, it seems that you must create a Share Extension target. See `private func openMainApp()` from this tutorial: https://medium.com/@damisipikuda/how-to-receive-a-shared-content-in-an-ios-application-4d5964229701 – olha Jul 23 '22 at 23:21
  • Check out this post about making a ShareExtension: https://stackoverflow.com/questions/14030823/sharing-a-link-opened-in-safari-in-ios-to-my-application/38037060#38037060 – Ars_Codicis Jul 23 '22 at 23:26

2 Answers2

2

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)
narek.sv
  • 845
  • 5
  • 22
  • This workaround looks promising! I'm guessing the share extension shares a URL with the main app, removing the requirement of needing to duplicate or connect data to the share extension? All of this using the onOpenURL? – Heron. F Jul 27 '22 at 01:11
  • yes! exactly. It just deep-links into the main app using the share url as a query parameter for the deep-link – narek.sv Jul 27 '22 at 05:54
  • At step 4: what customization needed to be done according to my deeplink? – Hridoy Chowdhury Jan 10 '23 at 11:45
  • Six months later and with plenty of failures, I've finally properly implemented this solution! The big, unbelievably minuscule problem was that my share extension pointed to a different iOS development build than the main app! I come to you with one more question, when I share something from within my app, and choose my share extension from inside my app, it crashes, how would I modify the code above to either block the share extension inside my app, or handle it better? I'm sure you have better things to do, but thought I'd ask :) – Heron. F Feb 02 '23 at 17:16
0

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

user16930239
  • 6,319
  • 2
  • 9
  • 33