RSSAmplifier

liman.io · Aug 29, 2022

Open Third-Party URLs in a SwiftUI App Using a Share Extension

0
Sign in to vote or save

liman.io

← Blog

iOS

SwiftUI

published on:

📚  This article is part of a series on 

iOS

 and 

SwiftUI

:


There are two widespread ways of opening an URL with a SwiftUI app, namely custom URL schemes and universal links. I do not cover their implementation in this article but there are a lot of great explanations readily available (like the one by Benoit Pasquier). Despite the great coverage for both features, I found myself struggling to implement a way of opening third-party URLs in catalyst.

Curtain up for share extensions

For my case, it is necessary to open URLs from GitLab (i.e., https://gitlab.com/) and from self-hosted instances of GitLab (i.e., https://gitlab.example.com/). In both cases, these URLs belong to a third party such that universal links are not an option. Likewise, custom URL schemes do not work since HTTP(S) is already an established URL scheme.

Hence, I need to come up with another solution for opening HTTP(S) URLs in a SwiftUI app. Luckily, Apple provides other means to interact with third-party content through app extensions. The action and share extensions are two candidates that fit for opening content from another app. While the action extension sounds like a better fit (“view or transform content originating in a host app”), the share extension provides a better user experience since the app’s icon is displayed directly at the top above the available actions.

In XCode, adding a share extension is possible from the app’s project targets. After selecting the “+” icon, choose the “Share Extension” template for iOS. In the next step, select a product name, select a team, and finish the setup. Since I do not work with Storyboard, I remove MainInterface.storyboard from the share extension’s folder and replace the key NSExtensionMainStoryboard from the extension’s Info.plist with the key NSExtensionPrincipalClass inside the NSExtension dictionary having a value of $(PRODUCT_MODULE_NAME).ShareViewController. Afterward, I replace the content of ShareViewController.swift with the following:

ShareViewController.swift


_43

import UIKit

_43

import SwiftUI

_43

_43

class ShareViewController: UIViewController {

_43

override func viewWillAppear(_ animated: Bool) {

_43

super.viewWillAppear(animated)

_43

_43

for item in extensionContext!.inputItems as! [NSExtensionItem] {

_43

if let attachments = item.attachments {

_43

for itemProvider in attachments {

_43

if itemProvider.hasItemConformingToTypeIdentifier("public.url") {

_43

itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (item, error) in

_43

let url = (item as! NSURL).absoluteURL!

_43

_43

self.open(url: url.toCatalystURL())

_43

self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)

_43

})

_43

}

_43

}

_43

}

_43

}

_43

}

_43

_43

private func open(url: URL) {

_43

var responder: UIResponder? = self as UIResponder

_43

let selector = #selector(openURL(_:))

_43

_43

while responder != nil {

_43

if responder!.responds(to: selector) && responder != self {

_43

responder!.perform(selector, with: url)

_43

_43

return

_43

}

_43

_43

responder = responder?.next

_43

}

_43

}

_43

_43

@objc

_43

private func openURL(_ url: URL) {

_43

return

_43

}

_43

}


@objc private func openURL(_ url: URL) is a declaration required by the compiler for private func open(url: URL) which mimics the lack of open(url: URL) from UIApplication. override func viewWillAppear(_ animated: Bool) implements the behavior necessary to handle the third-party URL shared from another application (e.g., a browser). The for loop may look intimidating but simply unpacks the shared third-party URL. Arriving at the shared third-party url, I perform the actual trick: converting the HTTPS URL into a custom URL scheme that I can open with the app. In my case, url.toCatalystURL() performs this conversion and passes the result to open(url: URL).

Caveats

As per App Store review guidelines, apps should only use public APIs. As such, using open(url: URL) in a share extension would violate the mentioned guidelines because ShareViewController does not derive UIApplication where open(url: URL) would be available. Apple also explicitly states this in their documentation: “A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class.” Since I haven’t published catalyst to the App Store, yet, I am not sure if such usage is prohibited in practice as well.

Code sharing

A share extension is a separate target, meaning that it does not share any source code with the corresponding app. If you still want to share source code between the app and the share extension, you have to change the target membership of the source code to be shared. In XCode, you can easily select the target membership by ticking the corresponding boxes under Target Membership from the inspector on the right-hand side after opening the desired source code file.

Debugging

Debugging an app extension is not straightforward but possible, regardless. While you cannot debug the app and its share extension at the same time, you can determine the target to be debugged. In XCode, navigate to Debug, Attach to process, and select the app extension’s process. Now, breakpoints should be triggered as you are used to with the containing app. Courtesy to mfaani for pointing this out on StackOverflow.

Read the original on liman.io

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.