Skip to main content

iOS SDK Integration (Swift)

This tutorial walks you through integrating the OpenLynk SDK into a native iOS app using Swift. By the end, your app will handle Universal Links, create shareable deep links, and restore deferred deep links after install.

Prerequisites

  • Completed the Quick Start tutorial (you have an app with an API key)
  • An iOS project in Xcode (Swift, iOS 13+)
  • Your App ID and API Key from the OpenLynk dashboard
  • A physical iOS device for testing (Universal Links do not work in the Simulator)

Step 1: Add the SDK

Add the OpenlynkSDK.swift file directly to your Xcode project. No package manager is required -- the SDK is a single self-contained file with no external dependencies.

  1. Download OpenlynkSDK.swift from the OpenLynk dashboard or repository
  2. Drag it into your Xcode project navigator
  3. Make sure "Copy items if needed" is checked and the file is added to your app target

Step 2: Get Your Credentials

In the OpenLynk dashboard, navigate to your app's settings page. You need two values:

CredentialWhere to find it
App IDApp settings page, displayed at the top
API KeyApp settings page, under "SDK API Key". Click Generate if you have not created one yet.

Step 3: Initialize the SDK in SceneDelegate

The recommended approach is to initialize the SDK in your SceneDelegate. This gives you access to the connection options for handling cold-start Universal Links.

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var openlynkSDK: OpenlynkSDK!

func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }

openlynkSDK = OpenlynkSDK(
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
config: OpenlynkSDKConfig(
autoRestoreOnInit: true,
userEmailProvider: { callback in
let email = AuthManager.shared.currentUserEmail
callback(email)
},
onRestoredLinks: { [weak self] links in
for link in links {
let path = link.destinationPath ?? link.destinationUrl
let params = link.parameters ?? link.metadata
self?.navigateTo(path: path, params: params)
}
},
onDeepLink: { [weak self] parsed in
self?.navigateTo(
path: parsed.destinationPath,
params: parsed.parameters
)
}
)
)

openlynkSDK.initSDK()

// Handle link if app was launched from a Universal Link
if let urlContext = connectionOptions.urlContexts.first {
openlynkSDK.handleIncomingURL(urlContext.url)
}
if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
openlynkSDK.handleIncomingURL(url)
}

let window = UIWindow(windowScene: windowScene)
window.rootViewController = UINavigationController(
rootViewController: HomeViewController()
)
self.window = window
window.makeKeyAndVisible()
}

private func navigateTo(path: String, params: [String: Any]) {
guard let nav = window?.rootViewController
as? UINavigationController else { return }

if path.hasPrefix("/product/") {
let productId = String(path.split(separator: "/").last ?? "")
let vc = ProductViewController(
productId: productId, sdk: openlynkSDK)
nav.pushViewController(vc, animated: true)
} else if path.hasPrefix("/profile/") {
let userId = String(path.split(separator: "/").last ?? "")
let vc = ProfileViewController(userId: userId)
nav.pushViewController(vc, animated: true)
}
}
}
note

Replace YOUR_APP_ID and YOUR_API_KEY with the real values from your dashboard. The userEmailProvider is optional but improves deferred deep link matching accuracy. If you do not have user authentication, you can omit it.

What Happens on initSDK()

When you call openlynkSDK.initSDK(), the SDK:

  1. Reports an install heartbeat (throttled to once per 24 hours)
  2. If autoRestoreOnInit is true, calls the restore API to check for pending deferred deep links
  3. Calls onRestoredLinks if any pending links are found

Unlike the Flutter SDK, the iOS SDK does not start an automatic link listener. You must call handleIncomingURL(_:) from your SceneDelegate (or AppDelegate) when a Universal Link arrives.

Add the following methods to your SceneDelegate to handle Universal Links when the app is already running:

// Universal Link -- app already running, opened via URL
func scene(_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
openlynkSDK.handleIncomingURL(url)
}

// Universal Link -- continue user activity
func scene(_ scene: UIScene,
continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
openlynkSDK.handleIncomingURL(url)
}

There are three scenarios for Universal Link arrival:

ScenarioMethod called
App not running (cold start)scene(_:willConnectTo:options:) via connectionOptions
App in backgroundscene(_:continue:)
App opened via custom URL schemescene(_:openURLContexts:)

AppDelegate Alternative

If your app does not use SceneDelegate, initialize the SDK in AppDelegate instead:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var openlynkSDK: OpenlynkSDK!

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
openlynkSDK = OpenlynkSDK(
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
config: OpenlynkSDKConfig(
autoRestoreOnInit: true,
onRestoredLinks: { links in /* navigate */ },
onDeepLink: { parsed in /* navigate */ }
)
)
openlynkSDK.initSDK()
return true
}

func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
openlynkSDK.handleIncomingURL(url)
return true
}
}

Step 5: Add Associated Domains

For Universal Links to work, you must configure Associated Domains in Xcode:

  1. Select your app target in Xcode
  2. Go to Signing & Capabilities
  3. Click + Capability and add Associated Domains
  4. Add the entry: applinks:YOUR_APP_SLUG.openlynk.to

Replace YOUR_APP_SLUG with your app's slug from the dashboard (e.g. applinks:myshop.openlynk.to).

Verify Your Configuration

Ensure the following match between Xcode and the OpenLynk dashboard:

  • Bundle ID in Xcode matches the iOS Bundle ID in the dashboard
  • Team ID in Xcode matches the iOS Team ID in the dashboard

OpenLynk automatically serves the AASA (Apple App Site Association) file at:

https://YOUR_APP_SLUG.openlynk.to/.well-known/apple-app-site-association

Visit this URL in a browser to verify it contains your appID in the format {TeamID}.{BundleID}.

warning

If you change your bundle ID or team ID in the dashboard, it may take up to 24 hours for Apple's CDN to pick up the updated AASA file. During development, you can delete and reinstall the app to force a refresh.

Step 6: Create a Share Button

Add a share button to a view controller that creates a deep link and presents the share sheet:

import UIKit

class ProductViewController: UIViewController {
private let productId: String
private let sdk: OpenlynkSDK

init(productId: String, sdk: OpenlynkSDK) {
self.productId = productId
self.sdk = sdk
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) { fatalError() }

override func viewDidLoad() {
super.viewDidLoad()
title = "Product \(productId)"
view.backgroundColor = .systemBackground

navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .action,
target: self,
action: #selector(shareTapped)
)
}

@objc private func shareTapped() {
sdk.createLink(
destination: "/product/\(productId)",
metadata: [
"product_id": productId,
"utm_source": "share",
"utm_medium": "ios_app"
]
) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let link):
let activityVC = UIActivityViewController(
activityItems: [
"Check out this product!",
link.url
],
applicationActivities: nil
)
self?.present(activityVC, animated: true)

case .failure(let error):
let alert = UIAlertController(
title: "Error",
message: "Could not create share link: "
+ "\(error.localizedDescription)",
preferredStyle: .alert
)
alert.addAction(
UIAlertAction(title: "OK", style: .default))
self?.present(alert, animated: true)
}
}
}
}
}

The createLink call is asynchronous. The completion handler returns a Result type with either a CreatedLink (containing url, id, slug) or an error.

Step 7: Test on a Physical Device

Universal Links do not work in the iOS Simulator. You must test on a real device.

  1. Install your app on the device via Xcode
  2. Open the Notes app on the device
  3. Type or paste your OpenLynk link URL (e.g. https://myshop.openlynk.to/1709876543-x7k9m2)
  4. Long-press the link -- you should see an option to "Open in [Your App]"
  5. Tap the link normally -- your app should open and onDeepLink should fire
tip

If the link opens in Safari instead of your app, try these steps:

  1. Delete the app and reinstall it (iOS caches AASA files on install)
  2. Verify the AASA URL is accessible in a browser
  3. Make sure you are not long-pressing and choosing "Open in Safari" -- just tap normally

Test Deferred Deep Linking

  1. Uninstall the app from the device
  2. Open your link in Safari on the device
  3. You will be redirected to the App Store (or your web fallback)
  4. Reinstall the app via Xcode
  5. Launch the app -- onRestoredLinks should fire with the original link data

Exit Checklist

  • OpenlynkSDK.swift added to the Xcode project
  • SDK initialized in SceneDelegate (or AppDelegate)
  • Universal Link handling implemented in all three scene methods
  • Associated Domains capability added with applinks: entry
  • Bundle ID and Team ID match between Xcode and the dashboard
  • Share button creates links via createLink
  • Deep linking tested on a physical device
  • Deferred deep linking tested after uninstall and reinstall

What's Next?