iOS SDK
Complete integration guide for the Encatch native iOS SDK — in-app feedback and survey collection for iOS apps
The Encatch iOS SDK lets you collect in-app feedback and surveys in native iOS apps. Display forms as a modal WebView overlay or inline in your layout, identify users, track screens and events, and submit responses to the Encatch backend.
The SDK is written in Swift, using URLSession for networking, UserDefaults for storage, and WKWebView for form rendering. It has no dependencies — nothing else to link, no embedded runtimes.
Overview
- Package:
encatch-swift(Swift Package Manager) - Version: 0.1.7
- Platforms: iOS 15+, macOS 12+ via Mac Catalyst (see the separate macOS page for Catalyst specifics)
- Repository: github.com/get-encatch/encatch-android (development happens under
ios-native/in the cross-platform monorepo;encatch-swiftis the SPM distribution mirror, updated per release) - License: MIT
Installation
File → Add Package Dependencies… and enter the package URL:
https://github.com/get-encatch/encatch-swiftSet the dependency rule to Up to Next Minor Version, then add the Encatch library to your app target.
dependencies: [
.package(url: "https://github.com/get-encatch/encatch-swift", from: "0.1.7"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "Encatch", package: "encatch-swift"),
]
),
]Pre-1.0 versioning
While versions are 0.x, minor bumps may contain breaking changes. SPM's from: "0.1.7" rule only auto-updates patch releases, which is the safe default — review the release notes before moving to a new minor version.
Quick Start
1. Initialization
Install the modal form host once at app launch with EncatchFormHost.install(), then initialize the SDK. EncatchFormHost.install() is required for modal forms — it mounts the listener that presents the form overlay on the topmost view controller.
import SwiftUI
import Encatch
@main
struct MyApp: App {
init() {
EncatchFormHost.install()
Task {
try await Encatch.shared.initialize(apiKey: "your-api-key")
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}The API is async/await — wrap calls in a Task { } when calling from synchronous contexts.
import UIKit
import Encatch
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
EncatchFormHost.install()
Task {
try await Encatch.shared.initialize(apiKey: "your-api-key")
}
return true
}
}Pass an optional EncatchConfig to customize SDK behavior:
try await Encatch.shared.initialize(
apiKey: "your-api-key",
config: EncatchConfig(
theme: .system,
isFullScreen: false,
debugMode: true,
appVersion: "1.2.3",
onBeforeShowForm: { payload in
// Return false to prevent the form from showing
return true
}
)
)Prop
Type
2. Identify users
Identify the current user. The userName is required (can be a username, email, or unique identifier). Traits and options are optional.
Username format
userName must be an ASCII identifier: 1–50 characters, using only letters A–Z / a–z, digits 0–9, and ., _, @, -. Spaces and non-English characters (Unicode, accented letters, emoji, etc.) are not supported. Use an email address, internal user ID, or ASCII username — for example user@example.com or user_123. To store a display name in another language, pass it as a trait instead (e.g. set: ["display_name": .string("…")]).
try await Encatch.shared.identifyUser(userName: "user@example.com")try await Encatch.shared.identifyUser(
userName: "user@example.com",
traits: UserTraits(
set: ["name": .string("Alice"), "plan": .string("team")]
)
)try await Encatch.shared.identifyUser(
userName: "user@example.com",
traits: UserTraits(
set: ["name": .string("Alice"), "plan": .string("team")],
setOnce: ["firstSeen": .string(ISO8601DateFormatter().string(from: Date()))],
increment: ["loginCount": 1],
decrement: ["credits": 5],
unset: ["trialEndDate"]
)
)Prop
Type
User traits support the following operations:
| Operation | Type | Description |
|---|---|---|
set | [String: JSONValue]? | Set user attributes (overwrites existing values) |
setOnce | [String: JSONValue]? | Set user attributes only if they don't already exist |
increment | [String: Double]? | Increment numeric user attributes |
decrement | [String: Double]? | Decrement numeric user attributes |
unset | [String]? | Remove user attributes |
Recommended
Using the secure option with a server-generated signature is recommended to verify that identification requests come from your backend. Keep your secret key on the server only — never expose it in client-side code.
Pass a server-generated HMAC signature so Encatch can validate the request. generatedDateTimeInUtc must be milliseconds since the Unix epoch (the string form of the epoch-milliseconds timestamp from your server). When your publishable key has a session timeout, use the same value in HMAC-SHA256(userName + epochMs, secretKey). It is sent as the X-User-Signature-Time header and limits the signature's lifespan.
try await Encatch.shared.identifyUser(
userName: "user@example.com",
options: IdentifyOptions(
secure: SecureOptions(
signature: "your-hmac-signature",
generatedDateTimeInUtc: "1741867200000" // ms since epoch (2025-03-13T12:00:00Z)
)
)
)3. Show a form manually
Show a specific form by slug or ID.
try await Encatch.shared.showForm("feedback-form")
try await Encatch.shared.showForm("feedback-form", options: ShowFormOptions(
reset: .always
))Prop
Type
Prop
Type
| ResetMode | Behavior |
|---|---|
.always | Reset pre-fill and response data on every form display |
.onComplete | Reset only after the form is completed |
.never | Never reset response data |
Pass caller context when showing a form. ContextValue supports .string, .number, .boolean, and .date(epochMillis:):
try await Encatch.shared.showForm("feedback-form", options: ShowFormOptions(
reset: .always,
context: [
"plan": .string("team"),
"feature": .string("checkout"),
"seats": .number(12),
"trial": .boolean(false),
]
))Other actions
Inline Forms
Inline forms are a way to show Encatch in-app feedback without a modal — the survey renders directly in your layout instead of as a full-screen overlay.
Build Your Own Form UX & UI
If your feedback flow uses a fixed, predictable question set — the same fields and workflow every time — you can build the form with your own native views and submit responses through the SDK. That keeps typography, spacing, colors, and interaction patterns aligned with the rest of your app, so the survey feels like a native screen rather than an embedded web page.
The flow has two parts:
- Intercept the form with
onBeforeShowFormand returnfalse— the SDK hands you the full form configuration (payload.formConfig) and skips its own UI. - Submit responses with
buildSubmitRequest+submitFormonce the user completes your native form.
try await Encatch.shared.initialize(
apiKey: "your-api-key",
config: EncatchConfig(
onBeforeShowForm: { payload in
guard payload.formId == "my-native-form" else { return true }
// Present your own UI using payload.formConfig
// (questionnaireFields, appearanceProperties, etc.)
await MyNativeSurveyPresenter.shared.present(config: payload.formConfig)
return false // SDK will not render its own form
}
)
)When the user finishes, map each answer to a NativeFormResponse and build the submit request. buildSubmitRequest covers all 33 question types — numeric scales take numbers, choice types take String or [String], boolean types take Bool, matrix types take dictionaries:
let responses = [
NativeFormResponse(questionId: "q1", type: "rating", value: 5),
NativeFormResponse(questionId: "q2", type: "short_answer", value: "Great product!"),
NativeFormResponse(questionId: "q3", type: "multiple_choice_multiple", value: ["speed", "design"]),
NativeFormResponse(questionId: "q4", type: "yes_no", value: true),
]
let request = buildSubmitRequest(
BuildSubmitRequestOptions(
formConfigurationId: formConfig.feedbackConfigurationId,
completionTimeInSeconds: 42
),
responses: responses
)
try await Encatch.shared.submitForm(request)Prop
Type
Support
- Package: github.com/get-encatch/encatch-swift
- Issues: github.com/get-encatch/encatch-android/issues (development monorepo — issues are welcome in either repo)
Was this page helpful?