LimeLink iOS SDK
Target SDK Version: 0.3.5 | iOS 12.0+ | Swift 5.0 | Xcode 14.0+
LimeLink iOS SDK provides Universal Link resolution, deferred deep links, and link event tracking through a binary XCFramework.
Version 0.3.5 is an immutable binary patch over 0.3.4. Its public runtime and API behavior are unchanged, and Objective-C and Objective-C++ module imports have been verified.
View previous iOS SDK releases
Requirements
| Item | Minimum |
|---|---|
| iOS Deployment Target | 12.0 |
| Swift | 5.0 |
| Xcode | 14.0+ |
| CocoaPods | 1.11.0+ |
Register the iOS Application in a LimeLink Project, create a dedicated SDK credential that is not reused by backend automation, and enable Associated Domains for the app target. The API key is included in app configuration and can be extracted from a shipped binary; keep it out of source control and logs, monitor its use, and rotate it after suspected exposure.
Binary Installation
Consumer releases are distributed as verified XCFramework binaries. The SDK source repository and manual source copying are not supported installation routes.
Swift Package Manager
In Xcode, select File > Add Package Dependencies and enter:
https://github.com/hellovelop/limelink-ios-sdk-binary.git
Select version 0.3.5, or add it to Package.swift:
dependencies: [
.package(
url: "https://github.com/hellovelop/limelink-ios-sdk-binary.git",
from: "0.3.5"
)
]
Then add the product to your app target:
.target(
name: "YourApp",
dependencies: [
.product(name: "LimelinkIOSSDK", package: "limelink-ios-sdk-binary")
]
)
CocoaPods
Version 0.3.5 supports all three Podfile integration modes.
Without use_frameworks!
platform :ios, '12.0'
target 'YourApp' do
pod 'LimelinkIOSSDK', '~> 0.3.5'
end
Dynamic Framework Linkage
platform :ios, '12.0'
use_frameworks!
target 'YourApp' do
pod 'LimelinkIOSSDK', '~> 0.3.5'
end
Static CocoaPods Linkage Mode
platform :ios, '12.0'
use_frameworks! :linkage => :static
target 'YourApp' do
pod 'LimelinkIOSSDK', '~> 0.3.5'
end
The CocoaPods static-linkage setting controls CocoaPods integration behavior; it does not convert the shipped dynamic XCFramework into a static binary.
Run pod install, then open the generated .xcworkspace.
Privacy Manifest
Version 0.3.5 includes an SDK-owned PrivacyInfo.xcprivacy in every XCFramework slice. The manifest declares the SDK's required-reason API use and deferred-link data practices, with tracking disabled.
The SDK manifest covers only LimeLink SDK behavior. Your app remains responsible for declaring its own required-reason APIs, collected data, tracking, and third-party SDK usage in App Store Connect and any app-owned privacy manifest.
Initialize the SDK
Initialize once during application launch:
import UIKit
import LimelinkIOSSDK
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
_ = DeepLinkManager.shared
let config = LimeLinkConfig(
apiKey: "YOUR_API_KEY",
loggingEnabled: false,
deferredDeeplinkEnabled: true
)
LimeLinkSDK.initialize(config: config)
return true
}
}
Register a long-lived listener before initialization when possible so it can receive the automatic launch outcome immediately.
Configure Universal Links
In Signing & Capabilities > Associated Domains, add:
applinks:limelink.org
applinks:*.limelink.org
For a custom domain, add its applinks: entry and provide a valid Apple App Site Association file.
AppDelegate
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
}
SceneDelegate
import LimelinkIOSSDK
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
if let activity = connectionOptions.userActivities.first,
activity.activityType == NSUserActivityTypeBrowsingWeb,
let url = activity.webpageURL {
LimeLinkSDK.shared.handleUniversalLink(url)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
LimeLinkSDK.shared.handleUniversalLink(url)
}
}
func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
if let url = contexts.first?.url {
LimeLinkSDK.shared.handleUniversalLink(url)
}
}
}
Receive Link Results
import LimelinkIOSSDK
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.resolvedUri else { return }
// Route destination in your app.
}
func onDeferredDeepLinkNotFound() {
// Normal outcome: continue the default launch flow.
}
func onDeeplinkError(error: LimeLinkError) {
print("LimeLink error [\(error.code)]: \(error.message)")
}
}
Listeners are weakly retained. Keep an app-wide listener alive, for example with static let shared. Public listener callbacks, manual completions, initialization errors, and pending replay are delivered on the main thread.
Result fields
| Field | Description |
|---|---|
originalUrl | Original URL, when available |
resolvedUri | Destination URI resolved by LimeLink |
queryParams | Query parameters from the original URL |
pathParams | Parsed mainPath and optional subPath |
isDeferred | true for an install-time deferred result |
Deferred Deep Links
With deferredDeeplinkEnabled: true, SDK initialization automatically attempts deferred resolution for an eligible first launch.
- A match calls
onDeeplinkReceivedwithisDeferred == true. - A normal no-match calls
onDeferredDeepLinkNotFound(). - A manual
matched,notFound, orfailedoutcome is delivered exactly once to its completion and exactly once to the current listener. - Completion and listener delivery both occur on the main thread; their relative order is not guaranteed.
- If automatic resolution completes before listener registration, the undelivered launch outcome is replayed once to the first listener.
- Concurrent automatic and manual checks share one request and notify listeners once.
- Transport, HTTP, or response-validation failures remain eligible for a later automatic retry.
- Public errors contain a stable code and safe message; internal URLs, payloads, backend bodies, and diagnostics are not exposed.
Manual usage:
LimeLinkSDK.shared.handleDeferredDeepLink { result, error in
if let result {
navigateTo(result.resolvedUri)
} else if let error {
print(error.message)
} else {
// Normal no-match outcome.
}
}
Deferred matching uses the app bundle identifier and bounded device context. Advertising identifiers are not required, and raw IP is not included in the SDK request body.
Stats Tracking
Resolved Universal Links are tracked automatically. To track a link explicitly:
if let url = URL(string: resolvedUri) {
LimeLinkSDK.shared.trackLinkStatus(url: url)
}
Objective-C
Import the generated binary module interface:
#import <LimelinkIOSSDK/LimelinkIOSSDK-Swift.h>
Initialize and handle a Universal Link through the public SDK facade:
LimeLinkConfig *config = [[LimeLinkConfig alloc] initWithApiKey:@"YOUR_API_KEY"
baseUrl:@"https://limelink.org/"
loggingEnabled:NO
deferredDeeplinkEnabled:YES];
[LimeLinkSDK initializeWithConfig:config];
[[LimeLinkSDK shared] handleUniversalLink:url];
Use LimeLinkListener for results. The removed UniversalLinkHandlerBridge is not part of the 0.3.5 consumer API.
Public API
| API | Purpose |
|---|---|
LimeLinkSDK.initialize(config:) | Initialize once |
addLinkListener(_:) | Register result callbacks |
removeLinkListener(_:) | Remove callbacks |
handleUniversalLink(_:) | Resolve a Universal Link |
handleDeferredDeepLink(completion:) | Explicitly check deferred resolution |
trackLinkStatus(url:) | Explicitly send link stats |
Use the LimeLinkSDK facade and public configuration/result models. Resolver services, URL parsers, request models, and state management are internal implementation details.
Migrating to 0.3.5
- Update the binary SPM or CocoaPods dependency to
0.3.5. - Keep a listener registered for automatic outcomes and use completion or listener as the single navigation owner for manual deferred checks.
- Treat
onDeferredDeepLinkNotFound()as a successful no-match outcome. - Confirm the SDK privacy manifest is included in the built app, then maintain separate declarations for app-owned behavior.
- Existing Swift and Objective-C facade integrations remain source compatible.
- CocoaPods consumers may integrate without
use_frameworks!, withuse_frameworks!, or withuse_frameworks! :linkage => :static; the shipped XCFramework remains dynamic. - Do not use withdrawn versions
0.3.1or0.3.2.
Troubleshooting
SDK not initialized
Call LimeLinkSDK.initialize(config:) before forwarding links or requesting deferred resolution.
Universal Link is not delivered
- Verify Associated Domains and the AASA file.
- Reinstall the app after changing Associated Domains.
- Test by tapping a link from another app; typing it into Safari does not trigger a Universal Link.
- Confirm AppDelegate or SceneDelegate forwards the URL.
Deferred result is not delivered
- Confirm deferred handling is enabled.
- Confirm the app bundle identifier matches the registered LimeLink application.
- Keep the listener alive and register it early.
- Treat
onDeferredDeepLinkNotFound()as a successful resolution with no match. - Retry transport failures on a later launch rather than treating them as no-match.
CocoaPods cannot find the module
Open the .xcworkspace, clean Derived Data, and confirm the selected pod version is 0.3.5 or later. Confirm the Podfile uses one of the supported linkage modes above, then rerun pod install.