LimeLink iOS SDK 1.0.1
iOS 12.0+ · Swift 5.0 · Xcode 14.0+
LimeLink iOS SDK handles direct links, Universal Links, deferred deep links, and explicit view tracking through a binary XCFramework. Version 1.0.1 delivers unresolved Universal Links through the unified success callback with a nullable destination.
View previous iOS SDK releases
Release availability
Swift Package Manager and CocoaPods 1.0.1 are publicly available.
Before you start
- Register the iOS Application in a LimeLink Project.
- Copy the canonical Project UUID, a public resource identifier suitable for app configuration.
- In Xcode Signing & Capabilities, add Associated Domains with
applinks:YOUR_LINK_HOSTfor each actual Link hostname (hostname only, no scheme or path). Register your app-owned scheme in the target's URL Types; these are separate settings. - Define an exact allowlist of destination schemes and HTTPS hosts.
Install
Swift Package Manager
dependencies: [
.package(
url: "https://github.com/hellovelop/limelink-ios-sdk-binary.git",
from: "1.0.1"
)
]
Add the LimelinkIOSSDK product to the app target.
CocoaPods
source 'https://cdn.cocoapods.org/'
platform :ios, '12.0'
pod 'LimelinkIOSSDK', '~> 1.0.1'
Do not use Git, path, source, or local-podspec fallback as a production installation route.
Initialize
Register a strongly retained listener early, then initialize once during launch:
import LimelinkIOSSDK
// Retain and register the listener before initialization starts Deferred lookup.
_ = DeepLinkManager.shared
let config = LimeLinkConfig(
projectId: "550e8400-e29b-41d4-a716-446655440000",
loggingEnabled: false
)
LimeLinkSDK.initialize(config: config)
Automatic deferred lookup runs for every eligible installation.
Forward incoming links
Forward Universal Links through the source-neutral API:
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
LimeLinkSDK.shared.handleIncomingLink(url)
return true
}
For an AppDelegate-owned custom scheme, add:
func application(
_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
LimeLinkSDK.shared.handleIncomingLink(url)
return true
}
For scene-based apps, merge these callbacks into the existing SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// Preserve the app's existing window/root-view setup.
for context in connectionOptions.urlContexts {
LimeLinkSDK.shared.handleIncomingLink(context.url)
}
for activity in connectionOptions.userActivities {
if activity.activityType == NSUserActivityTypeBrowsingWeb,
let url = activity.webpageURL {
LimeLinkSDK.shared.handleIncomingLink(url)
}
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
LimeLinkSDK.shared.handleIncomingLink(url)
}
}
func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
for context in contexts { LimeLinkSDK.shared.handleIncomingLink(context.url) }
}
Use the callbacks owned by your app's lifecycle, not duplicate forwarding from both delegates. The snippets assume these events belong to LimeLink; preserve other SDKs' handlers and filter app-owned schemes/hosts before forwarding when multiple handlers share a callback. Incoming URLs still require destination validation before navigation.
Pre-initialization links use a process-memory capacity-10 FIFO. Direct and Universal duplicates with the same source and exact inbound URL are suppressed while pending/in flight and for five seconds after success. Processing is independent, so callback order can differ from ingress order.
Receive results
final class DeepLinkManager: NSObject, LimeLinkListener {
static let shared = DeepLinkManager()
private override init() {
super.init()
LimeLinkSDK.shared.addLinkListener(self)
}
func onDeeplinkReceived(result: LimeLinkResult) {
guard let destination = result.deeplinkUrl ?? result.originalUrl,
isAllowedDestination(destination) else { return }
navigateTo(destination)
}
func onDeferredDeepLinkNotFound() {
// Normal live-only no-match outcome.
}
func onDeeplinkError(error: LimeLinkError) {
showSafeFallback(error.code, error.message)
}
}
LimeLinkResult contains:
| Field | Contract |
|---|---|
deeplinkUrl | Nullable resolved app destination; nil when a Universal Link is unresolved or unregistered |
source | directDeepLink, universalLink, or deferredDeepLink |
originalUrl | Nullable original inbound/server URL |
isDeferred | true only for Deferred results |
All public callbacks run on the main thread. Successful results completed without a listener use a capacity-10, drop-oldest, process-memory FIFO and replay once to the first later listener. Errors and deferred not-found outcomes are live-only.
For a resolved result, route deeplinkUrl. For an unresolved Universal Link, deeplinkUrl is nil, originalUrl preserves the exact inbound HTTPS URL, source is universalLink, and no separate error callback is emitted. If the app chooses browser fallback, use deeplinkUrl ?? originalUrl, then parse and exactly compare the selected URL's scheme and host with an app-owned allowlist before navigation.
Manual deferred handling
LimeLinkSDK.shared.handleDeferredDeepLink { result, error in
if let result,
let destination = result.deeplinkUrl ?? result.originalUrl {
guard isAllowedDestination(destination) else { return }
navigateTo(destination)
} else if let error {
showSafeFallback(error.code, error.message)
} else {
// Normal not-found result.
}
}
Matched, not-found, and failed outcomes reach both the completion and current listener exactly once; their relative order is unspecified. Choose one channel as navigation owner.
Explicit stats
Dynamic Lookup already owns lookup attribution. Use trackLinkStatus(url:) only for a separate app-owned view event; it is best effort and should not duplicate a Universal Link event.
Privacy
The XCFramework includes an SDK-owned privacy manifest in every slice. The app remains responsible for its own required-reason APIs, data practices, third-party disclosures, and final merged privacy report.
Verify and Troubleshoot
- Confirm the Project has one active iOS Application with the expected Bundle ID, app scheme, and store ID.
- Confirm the app entitlement contains each LimeLink or Custom Domain in Associated Domains.
- Test cold and warm Universal Links plus an app-owned custom scheme.
- Test resolved, unresolved, deferred matched, deferred not-found, and safe error behavior.
If callbacks do not arrive, verify the listener is strongly retained and registered before initialization, lifecycle URLs are forwarded once, and the SPM/CocoaPods artifact is 1.0.1. If a web link returns deeplinkUrl == nil, treat it as the documented unresolved success and use originalUrl only after exact allowlist validation.
Migrating from 1.0.0
- Treat
deeplinkUrlas nullable. - Handle unresolved Universal Links as successful results and use the exact
originalUrlonly for an allowlisted browser fallback.
Migrating from 0.4.1
- Replace removed
handleUniversalLink(_:)withhandleIncomingLink(_:). - Replace
resolvedUriwith nullabledeeplinkUrl. - Remove
queryParamsandpathParams; parsedeeplinkUrlin the app when needed. - Use
sourceandoriginalUrlfor source-aware behavior. - Expect native capacity-10 success replay and exact-URL five-second deduplication.
- Keep automatic deferred lookup enabled and choose one completion/listener navigation owner.