LimeLink iOS SDK
Target SDK Version: 0.4.0 | iOS 12.0+ | Swift 5.0 | Xcode 14.0+
LimeLink iOS SDK provides Universal Link resolution, deferred deep links, and explicit link event tracking through a binary XCFramework.
Version 0.4.0 moves SDK-owned networking to the Mobile API V2 contract and requires the public Project UUID for initialization. It no longer uses an Organization API credential as native SDK identity.
View previous iOS SDK releases
Before You Start
- Create or open a LimeLink Project.
- Register the iOS Application in that Project.
- Copy the Project ID shown in Project settings. It must be a canonical UUID and is not a secret.
- Decide which destination schemes and HTTPS hosts your app will allow before routing a resolved URI.
Installation
The supported consumer routes are binary Swift Package Manager and binary CocoaPods. Do not use the SDK source repository as an app dependency.
Swift Package Manager
Add this package in Xcode:
https://github.com/hellovelop/limelink-ios-sdk-binary.git
Select version 0.4.0, or add it to Package.swift:
dependencies: [
.package(
url: "https://github.com/hellovelop/limelink-ios-sdk-binary.git",
from: "0.4.0"
)
]
Add the LimelinkIOSSDK product to your app target.
CocoaPods
source 'https://cdn.cocoapods.org/'
platform :ios, '12.0'
target 'YourApp' do
pod 'LimelinkIOSSDK', '~> 0.4.0'
end
The shipped XCFramework is dynamic and is verified without use_frameworks!, with use_frameworks!, and with use_frameworks! :linkage => :static.
Privacy Manifest
Version 0.4.0 includes an SDK-owned PrivacyInfo.xcprivacy in every XCFramework slice. It declares SDK-required UserDefaults access and deferred-link data practices with tracking disabled.
Your app remains responsible for its own required-reason APIs, collected data, tracking declarations, third-party SDK disclosures, and the final merged privacy report.
Initialize the SDK
Initialize once during app launch with the Project UUID:
import LimelinkIOSSDK
let config = LimeLinkConfig(
projectId: "550e8400-e29b-41d4-a716-446655440000",
loggingEnabled: false,
deferredDeeplinkEnabled: true
)
LimeLinkSDK.initialize(config: config)
baseUrl defaults to https://api.limelink.org/. Production apps normally should keep the default. Duplicate initialization calls are ignored.
Configure Universal Links
Add the domains your app handles to Associated Domains:
applinks:limelink.org
applinks:*.limelink.org
applinks:links.example.com
For a Project Custom Domain, publish the correct Apple App Site Association file for that hostname. Register every allowed custom callback scheme in CFBundleURLSchemes.
Forward browsing web activities to the SDK:
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.handleUniversalLink(url)
return true
}
For scenes:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
LimeLinkSDK.shared.handleUniversalLink(url)
}
Custom-scheme app-open events belong to your app router; do not send them to handleUniversalLink(_:) for network resolution.
V2 Link Resolution
The SDK accepts an absolute HTTPS inbound URL with a host and a supported Link path. It sends the complete OS-provided URL once as full_request_url, preserving ordered and duplicate query items.
The URI returned by LimeLink is final. The SDK does not append the inbound query again. resolvedUri preserves the final URI, while queryParams is a dictionary view in which the last duplicate value wins.
Receive Results
Listeners are weak. Retain a long-lived listener and register it early:
final class DeepLinkManager: NSObject, LimeLinkListener {
static let shared = DeepLinkManager()
private override init() {
super.init()
LimeLinkSDK.shared.addLinkListener(self)
}
func onDeeplinkReceived(result: LimeLinkResult) {
guard isAllowedDestination(result.resolvedUri) else { return }
navigateTo(result.resolvedUri)
}
func onDeferredDeepLinkNotFound() {
// Normal terminal result: continue the default launch flow.
}
func onDeeplinkError(error: LimeLinkError) {
// Show a safe fallback without exposing request details.
}
}
Before opening a resolved URI, parse it and compare its scheme and host against an explicit app-owned allowlist. SDK validation does not make arbitrary destinations trusted.
An automatic launch outcome completed before registration is retained in process memory and replayed once to the first listener. Public callbacks and pending replay run on the main thread.
Deferred Deep Links
Automatic deferred resolution is enabled by default. The SDK sends the configured Project UUID and bounded screen, language, timezone, OS, and canonical phone/tablet evidence. It does not include raw IP in the request body and applies a five-second network timeout.
You can request a fresh manual check:
LimeLinkSDK.shared.handleDeferredDeepLink { result, error in
switch (result, error) {
case let (result?, nil):
guard isAllowedDestination(result.resolvedUri) else { return }
navigateTo(result.resolvedUri)
case (nil, nil):
// Normal not-found outcome.
break
case let (nil, error?):
showError(error.message)
default:
break
}
}
Manual matched, not-found, and failed outcomes are each delivered exactly once to both the completion and the current listener. Both channels run on the main thread; their relative order is unspecified. Automatic and manual checks can share one in-flight request. A transport, server, or validation failure remains eligible on a later launch, but the SDK does not immediately loop retries.
Explicit Stats
Successful Dynamic Lookup already records lookup attribution, so the SDK does not send a second automatic Stats request.
Use explicit tracking only when your app intentionally owns a separate view event. Pass the original inbound LimeLink URL, not the server-returned destination URI:
LimeLinkSDK.shared.trackLinkStatus(url: inboundLimeLinkURL)
Explicit Stats is best effort. Failure never changes Link delivery, and HTTP acceptance is not a guarantee that a metric incremented.
Objective-C
Import the generated Swift interface and initialize with Project ID:
#import <LimelinkIOSSDK/LimelinkIOSSDK-Swift.h>
LimeLinkConfig *config = [[LimeLinkConfig alloc]
initWithProjectId:@"550e8400-e29b-41d4-a716-446655440000"
baseUrl:@"https://api.limelink.org/"
loggingEnabled:NO
deferredDeeplinkEnabled:YES];
[LimeLinkSDK initialize:config];
[[LimeLinkSDK shared] handleUniversalLink:url];
Objective-C and Objective-C++ module imports are supported. For manual deferred handling use handleDeferredDeepLinkWithCompletion:; (result, nil) is matched, (nil, nil) is not found, and (nil, error) is failed.
Public API
| API | Purpose |
|---|---|
LimeLinkSDK.initialize(config:) | Initialize once with Project ID |
addLinkListener(_:) | Register a weak result listener |
removeLinkListener(_:) | Remove a listener |
handleUniversalLink(_:) | Resolve an inbound HTTPS Link |
handleDeferredDeepLink(completion:) | Perform a manual deferred check |
trackLinkStatus(url:) | Send an explicit best-effort view event |
Use the facade and public configuration/result models. Resolver services, URL parsers, request models, and state management are internal.
Migrating from 0.3.5
- Update the binary SPM or CocoaPods dependency to
0.4.0. - Replace API-key initialization with the required canonical Project UUID.
- Expect V2 Dynamic Lookup, Deferred, and explicit Stats behavior on
https://api.limelink.org/. - Treat the server-returned URI as final; do not append source query items again.
- Do not send an automatic or duplicate Stats event after successful lookup.
- Register all custom callback schemes and apply an app-owned scheme/host allowlist before navigation.
- Keep one navigation owner when using both listener and manual completion callbacks.
- Existing Objective-C support, Deferred tri-state, exactly-once manual delivery, main-thread callbacks, and unspecified completion/listener order remain supported.
Troubleshooting
SDK not initialized
Call LimeLinkSDK.initialize(config:) with a valid Project UUID before forwarding links or requesting deferred resolution.
Universal Link is not delivered
- Verify Associated Domains and the AASA file.
- Confirm the inbound URL is absolute HTTPS with a supported Link path.
- Forward only
NSUserActivityTypeBrowsingWebactivities. - Retain and register the listener early.
- Check the safe SDK error callback; do not log full inbound or resolved URLs.
CocoaPods cannot find the module
Open the .xcworkspace, clean Derived Data, confirm the selected pod version is 0.4.0 or later, and rerun pod install using the CocoaPods CDN.