# Deep linking

> Handle incoming deep links in your iOS app with the Grovs SDK.

Source: https://docs.grovs.io/docs/sdk/ios/deep-linking

Grovs delivers each opened link's payload to your `GrovsDelegate`. Route the user from there.

## Receiving deep link events

Conform to `GrovsDelegate`:

```swift
class YourViewController: UIViewController, GrovsDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        Grovs.delegate = self
    }

    func grovsReceivedPayloadFromDeeplink(link: String?, payload: [String: Any]?, tracking: [String: Any]?) {
        // Route the user based on payload data
        if let screen = payload?["screen"] as? String {
            navigateTo(screen)
        }
    }
}
```

> **Warning:** `Grovs.delegate` is held **weakly**. Use an object that lives as long as the app, such as your `AppDelegate` or a long-lived coordinator. If it's deallocated (a dismissed view controller, say), payloads stop until you set a new one.

Set the delegate as early as possible, ideally through `configure`. If a link opens the app before `configure` runs, the SDK keeps the most recent link and handles it once configured. This is common when a React Native or Flutter bridge configures the SDK. Read payloads that arrived with no delegate set through [`lastReceivedPayload`](#retrieving-past-payloads).

If a link's lookup fails (the device is offline, say), the SDK reports `.networkRequestFailed` and tries the link again at the next foreground within five minutes. After that it's dropped, so the app doesn't navigate long after the tap.

`handleAppDelegate(open:options:)` returns `false` for a custom scheme other than your project's Grovs URL scheme, such as a sign-in callback, so a handler after Grovs in an `||` chain still gets it. Such a URL doesn't replace the link the app was opened with.

## Handling SDK errors

Implement the optional `grovsDidEncounterError` callback to send failures to your own logging. Since 3.1 it's an `@objc optional` method. If your signature doesn't match exactly, Xcode only shows a "nearly matches" warning and the method is never called.

```swift
func grovsDidEncounterError(_ error: GrovsError, message: String) {
    Analytics.log("grovs_error", ["code": error.description, "message": message])
}
```

| Case                    | Meaning                                                                                                                                                                           |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.authenticationFailed` | Authentication with the backend failed. Check the API key and bundle ID.                                                                                                          |
| `.networkRequestFailed` | A request failed after retries, or a link lookup failed. The SDK backs off and retries later.                                                                                     |
| `.eventDispatchFailed`  | An event batch failed to send. The events stay queued.                                                                                                                            |
| `.linkGenerationFailed` | Link generation failed.                                                                                                                                                           |
| `.inAppDisplayFailed`   | An [in-app message](https://docs.grovs.io/docs/sdk/ios/in-app-messages) couldn't be drawn, for example because the app has no window. It retries at its next trigger. New in 3.1. |

Errors are informational. The SDK recovers on its own. Both delegate methods run on the **main thread**.

## Clipboard-assisted deferred deep linking

When a copy-to-clipboard link opens in a browser, the preview page copies it to the clipboard before sending the visitor to the App Store. On first launch after install, the SDK reads it and attributes the install deterministically. The deferred link arrives through `grovsReceivedPayloadFromDeeplink`, like a fingerprint match.

No integration is needed. How the check runs:

- It's armed on the first launch with the SDK: a fresh install, or the first launch of an update that adds Grovs.
- It runs once. If it can't complete (the device is offline, say), it retries on a later launch or foreground until it resolves, then never again.
- iOS may show its system paste confirmation. Denying it leaves the install unattributed.
- It's skipped when the backend reports no recent clicks on your clipboard-enabled links (currently a 48-hour window), and when the clipboard holds no web URL. Otherwise the prompt can appear even if the clipboard URL isn't a Grovs link.

If your links use a custom domain, list it so the SDK recognizes them. Content on any other host is never read or sent:

```swift
Grovs.configure(
    APIKey: "your-api-key",
    useTestEnvironment: false,
    clipboardDomains: ["links.yourdomain.com"],
    delegate: self
)
```

Enable copy-to-clipboard per link with [`copyToClipboardiOS`](https://docs.grovs.io/docs/sdk/ios/link-creation), or set the project default in the dashboard. Flow, settings and limits: [Copy to Clipboard](https://docs.grovs.io/docs/links/copy-to-clipboard).

## Retrieving past payloads

Most recent payload:

```swift
Grovs.lastReceivedPayload { payload in
    print("Last payload: \(payload)")
}
```

All payloads since app launch:

```swift
Grovs.allReceivedPayloadsSinceStartup { payloads in
    guard let payloads = payloads else { return }
    for payload in payloads {
        print("Payload: \(payload)")
    }
}
```

## Getting link details

Fetch the details for a link path:

```swift
Grovs.linkDetails(path: "/my-link-path") { details in
    print("Link details: \(details)")
}
```
