Open Third-Party URLs in a SwiftUI App Using a Share Extension
Tackling
AttributeGraph precondition failure: setting value during update
using Realm in SwiftUIInfinite Scrolling List for Paginated Results from GraphQL with SwiftUI
Open Third-Party URLs in a SwiftUI App Using a Share Extension
Synced Realm on iOS with SwiftUI using Sign-in with Apple for Authentication
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:
@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.