# iOS setup checklist

> Verify your iOS integration end to end: dashboard values, Associated Domains, URL schemes, SDK logs, and every deep link flow before you ship.

Source: https://docs.grovs.io/docs/troubleshooting/ios-setup-checklist

Check an iOS integration from the dashboard to the device before release. Each step has a check and an expected result. If a step fails, stop: later steps depend on it.

Run every check twice, for **test** and **production**. Each environment has its own link domain and URL scheme, and most broken setups configure only one.

## 1. Dashboard values match the app

Compare your project in the [Grovs dashboard](https://app.grovs.io) against Xcode:

| Dashboard field            | Real value                                                                                                                 | Check                                                                                                                                |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Bundle Identifier          | Target → **General** → **Bundle Identifier**                                                                               | Exact match, including case. See [How to Get the Bundle Identifier](https://docs.grovs.io/docs/how-to-guides/ios/bundle-identifier). |
| Apple App Prefix (Team ID) | Target → **Signing & Capabilities** → **Team**, or [developer.apple.com](https://developer.apple.com/account) → Membership | Exact match. See [How to Get the Apple App Prefix](https://docs.grovs.io/docs/how-to-guides/ios/apple-app-prefix).                   |
| Link domains               | Dashboard project setup, one per environment                                                                               | Note both for step 2.                                                                                                                |
| URL schemes                | Dashboard project setup, one per environment                                                                               | Note both for step 3.                                                                                                                |

Apple's association file identifies your app as `TEAMID.bundle.identifier`. A typo in either part breaks Universal Links for every user, and shows only once a build with the wrong value is installed.

## 2. Associated Domains are set up

Under **Signing & Capabilities → Associated Domains**, add an `applinks:` entry for **each** link domain:

```text
applinks:medly.sqd.link
applinks:medly.test-sqd.link
```

Use the exact hostnames the dashboard shows, plus any [custom domain](https://docs.grovs.io/docs/links/custom-domains). See [How to Add an Associated Domain](https://docs.grovs.io/docs/how-to-guides/ios/associated-domain).

**Check the association file.** For each domain, open:

```text
https://<your-link-domain>/.well-known/apple-app-site-association
```

It must return JSON listing your `TEAMID.bundle.identifier` under `applinks`. If it doesn't, or returns a 404, see [Check the file Grovs serves](https://docs.grovs.io/docs/troubleshooting/deep-link-not-opening-app#1-check-the-file-grovs-serves).

**Check the entitlement is in the build.** A build signed with a provisioning profile created before you added the capability silently lacks it:

```bash
codesign -d --entitlements :- /path/to/YourApp.app | grep -A3 associated-domains
```

The output should list the same `applinks:` entries.

> **Warning:** iOS fetches the association file through Apple's CDN when the app is installed, and the CDN can lag behind a corrected file. After fixing step 1 or 2, delete the app and reinstall it.

### Check the association on the device

These tools live under **Settings → Developer**, which appears once the device has been used with Xcode. On iOS 16 and later, turn on **Settings → Privacy & Security → Developer Mode** first.

- **Diagnostics.** **Settings → Developer → Universal Links → Diagnostics**: paste a Grovs link and tap **Diagnose**. Run it for both link domains.
- **Skip the CDN.** Add `?mode=developer` to each `applinks:` entry in a development build and turn on **Associated Domains Development**. The device then fetches the file from your domain directly. Turn it off before testing a release candidate.
- **System logs.** Filter **Console.app** on the `swcd` process, or read `swcutil_show.txt` from a sysdiagnose.

[Read what iOS decided](https://docs.grovs.io/docs/troubleshooting/deep-link-not-opening-app#6-read-what-ios-decided) explains each Diagnostics result and `swcutil_show.txt` field. On a Mac, `sudo swcutil show` gives the same state for a Mac Catalyst or Designed for iPad build.

## 3. URL schemes are registered

Under **Info → URL Types**, add one entry per scheme from the dashboard, with **Role** set to **Editor**. See [How to Add a New URL Scheme](https://docs.grovs.io/docs/how-to-guides/ios/url-scheme).

**Check from the SDK.** After authenticating, the SDK compares the current environment's scheme with your `Info.plist`. With info logging on ([step 5](#5-the-sdk-authenticates)) you see one of:

```text
URL Scheme properly configured.
There's a mismatch between the URL Scheme in the project and the one from the dashboard, deeplinking won't function properly!
```

A mismatch means the scheme for your `useTestEnvironment` setting is missing from **URL Types**, or its role isn't Editor.

**Check from the device.** Type `yourscheme://open` into Safari's address bar. Safari should ask to open your app. If not, the scheme isn't in the installed build.

## 4. Delegate calls are forwarded

The SDK only sees Universal Links and URL schemes if you forward the system callbacks:

- **SceneDelegate apps:** `scene(_:openURLContexts:)`, `scene(_:continue:)` and `scene(_:willConnectTo:options:)`.
- **AppDelegate-only apps:** `application(_:continue:restorationHandler:)` and `application(_:open:options:)`.

Copy them from [Delegate setup](https://docs.grovs.io/docs/sdk/ios/quick-start#delegate-setup). `willConnectTo` is the one people forget. It handles a link that **launches** the app; without it, links work only while the app is running.

Pass the delegate to `configure`, or set `Grovs.delegate` after it. Keep a strong reference: the SDK holds the delegate **weakly**, so one created inline is released and never called.

SwiftUI apps on the `App` lifecycle need a `UIApplicationDelegateAdaptor`, plus a scene delegate through `application(_:configurationForConnecting:options:)`. `onOpenURL` alone doesn't reach the SDK.

## 5. The SDK authenticates

Turn on info logging before `configure` and run a debug build:

```swift
Grovs.setDebug(level: .info)
Grovs.configure(APIKey: "your-api-key", useTestEnvironment: true, delegate: self) { success in
    print("Grovs configured: \(success)")
}
```

Expected in the Xcode console, then `Grovs configured: true`:

```text
Test environment enabled.          // only when useTestEnvironment is true
Authenticate
URL Scheme properly configured.
```

| Console line                                                                                                                                     | Meaning                                                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `[authentication_failed] Authentication failed. Check API key and bundle ID.` then `Can not initialize the SDK, the Bundle Key combo is invalid` | The key isn't for a project with this bundle ID, it's the other environment's key, or there's no network. Check the key, step 1, then connectivity. The SDK retries when the app becomes active. |
| `[authentication_failed] URI schemes are not configured. SDK won't work.`                                                                        | The app has no URL types. Go to step 3.                                                                                                                                                          |
| `baseURL is not a valid URL: ... The SDK was not configured.`                                                                                    | A self-hosted `baseURL` without a host. `configure` reports `false` and stops.                                                                                                                   |
| `The SDK is configured but disabled — skipping authentication.`                                                                                  | You passed `enabled: false`. Expected when gating on consent; see [Privacy & Consent](https://docs.grovs.io/docs/sdk/ios/privacy).                                                               |

Release builds redact log content to `<private>` in Console.app.

> **Warning:** Match `useTestEnvironment` to the API key: **test key → `true`**, **production key → `false`**. A link from the other environment resolves to nothing. See [Test vs production environment mismatch](https://docs.grovs.io/docs/troubleshooting/sdk-configuration-issues#test-vs-production-environment-mismatch).

## 6. Test each flow

Create a test link in the dashboard with a payload you can recognize, such as `{"screen": "checklist", "id": "42"}`. Test on a **physical device**: Universal Links don't work in the Simulator, only the URL scheme flow does.

For Universal Links and deferred links, `grovsReceivedPayloadFromDeeplink` should fire on the main thread with `link` set to the URL and `payload` holding your values.

| Flow                        | How to test                                                                                        | If it fails                                                                                     |
| --------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Universal Link, app running | Paste the link into **Notes** and tap it.                                                          | Recheck step 2. Never test from Safari's address bar: a typed or pasted URL never opens an app. |
| Universal Link, cold start  | Force-quit the app, then tap the link again.                                                       | `willConnectTo` (or the AppDelegate equivalent) isn't forwarded. Go to step 4.                  |
| URL scheme                  | Open the scheme from Notes, or run `xcrun simctl openurl booted "yourscheme://"` in the Simulator. | Recheck step 3.                                                                                 |

The SDK sends every scheme URL to the backend, so a payload only arrives for a URL that identifies a Grovs link. Opening the bare scheme proves the registration and the `open:options:` / `openURLContexts` forwarding. Universal Links fall back to this path when a user opts out, so it must work too.

**Restore a switched-off Universal Link.** If someone tapped **Open in Safari** on the Universal Link banner, iOS opens Safari for that domain from then on. Long-press the link and choose **Open in "YourApp"**. The setting is per device.

### Deferred deep link

This flow covers users who tap a link **before** installing. Use a real install path, not Xcode:

1. Delete the app.
2. Open the link on the device in Safari. It lands on the store page, or the preview page first if enabled.
3. Install from TestFlight or the App Store and open the app.

The delegate fires on first launch with the payload. An Xcode install skips the click-to-install path the SDK matches on. See [Deferred deep linking](https://docs.grovs.io/docs/troubleshooting/deferred-deep-linking).

If copy-to-clipboard is enabled for the link or project, iOS may show a paste prompt on first launch. Allow it. With info logging on, the `Clipboard flow -` lines show what the SDK found. See [Clipboard-assisted deferred deep linking](https://docs.grovs.io/docs/sdk/ios/deep-linking#clipboard-assisted-deferred-deep-linking).

### Confirm from the dashboard

Each resolved link records an open, and a fresh install records an install. Check that the link's click and open counts moved in the environment you tested. If the device got the payload but the dashboard shows nothing, you're viewing the other environment.

## 7. Before you ship

- Repeat steps 2, 3 and 6 on a **production** build: production API key, `useTestEnvironment: false`, production link. TestFlight is the right place.
- Confirm the production `applinks:` entry and URL scheme are in the target, not only the test ones.
- Remove `?mode=developer` and turn **Associated Domains Development** off, so the final check goes through Apple's CDN like your users.
- For a custom domain, confirm its association file loads over HTTPS with a valid certificate. iOS rejects it otherwise. See [Custom Domains](https://docs.grovs.io/docs/links/custom-domains).
- Leave `setDebug` at the default `.error`, or remove the call. Release builds redact logs either way.

## See also

- [Deep link doesn't open the app](https://docs.grovs.io/docs/troubleshooting/deep-link-not-opening-app): debugging links that open Safari
- [SDK configuration issues](https://docs.grovs.io/docs/troubleshooting/sdk-configuration-issues): initialization failures and environment mismatches
- [iOS Deep Linking](https://docs.grovs.io/docs/sdk/ios/deep-linking): the delegate, error callbacks and past payloads
- [iOS Quick Start](https://docs.grovs.io/docs/sdk/ios/quick-start): configuration and delegate setup
