macOS SDK
Run the Encatch Swift SDK on macOS via Mac Catalyst — in-app feedback and survey collection for Mac apps
The Encatch Swift SDK runs on macOS through Mac Catalyst. It is the same package as the iOS SDK — no separate dependency, no conditional code paths in your integration. This page covers what's specific to the Mac: enabling Catalyst, and building a UI around the SDK that feels like a real Mac app rather than a stretched phone screen.
Overview
- Package:
encatch-swift— the same Swift Package as iOS - Version: 0.1.7
- Platforms: iOS 15+, macOS 12+
- License: MIT
macOS support works in two tiers:
| Target type | What works |
|---|---|
| Mac Catalyst app | Everything — modal form overlay, inline forms, tracking, identify, sessions |
| Plain macOS (AppKit / SwiftUI-for-Mac) target | Core APIs compile and run — initialize, identifyUser, trackEvent, trackScreen, sessions — but the WebView form UI does not, because it requires UIKit |
Why Catalyst?
The SDK's form UI (EncatchFormHost, EncatchInlineFormView, the WebView bridge) is built on UIKit and compiled behind #if canImport(UIKit). Mac Catalyst provides UIKit on macOS, so the full SDK — including forms — works in a Catalyst app with zero changes. A plain macOS target gets the headless tracking/identify core only.
For the complete API reference — configuration, identify, events, sessions, inline forms, interceptors — see the iOS SDK page. The API is identical.
Installation
1. Enable Mac Catalyst on your app target
In Xcode, select your iOS app target → General → Supported Destinations → add Mac (Mac Catalyst). Choose "Optimize Interface for Mac" for native-feeling controls, or "Scaled to Match iPad" for a literal iPad port (not recommended — see macOS-specific considerations below).
2. Add the package
File → Add Package Dependencies… → enter:
https://github.com/get-encatch/encatch-swiftDependency rule: Up to Next Minor Version from 0.1.7, then add the Encatch product 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.
No separate Catalyst build settings are needed — the package builds for the maccatalyst destination as-is.
Quick Start
Install the modal form host once at launch, initialize, and show a form. This is exactly the same code as iOS:
import SwiftUI
import Encatch
@main
struct MyMacApp: App {
init() {
EncatchFormHost.install()
}
var body: some Scene {
WindowGroup {
ContentView()
.task {
try? await Encatch.shared.initialize(apiKey: "YOUR_API_KEY")
try? await Encatch.shared.identifyUser(userName: "user@example.com")
}
}
}
}Then trigger a form from anywhere — a button, a menu item, a keyboard shortcut:
Task {
try await Encatch.shared.showForm("your-form-id")
}The API surface is identical to iOS, so refer to the iOS page for:
- Configuration (
EncatchConfig) —apiBaseUrl,webHost,theme,isFullScreen,debugMode,appVersion,onBeforeShowForm - Identify users — traits, secure identification
- Events and screens —
trackEvent,trackScreen - Inline forms —
EncatchInlineFormView - Form events and CTAs —
Encatch.shared.on { eventType, payload in … } - Sessions —
startSession,pauseSession,stopSession,resetUser,clearAll
macOS-specific considerations
The SDK needs nothing extra to run under Catalyst — but your app around it should behave like a Mac app. The guidance below comes from building a full Catalyst tester app against the SDK; the Catalyst limitations listed are real compiler/runtime behavior verified by building, not assumptions.
Design Mac-native, don't port the phone UI
Running an iPhone layout unmodified under Catalyst looks like a stretched phone screen. Patterns that translate well:
- Sidebar, not bottom tabs — replace a
TabViewbottom bar withNavigationSplitView(Mail/Xcode-style). A sidebar also gives you a persistent, always-visible place to surface state — for example, a badge count of forms youronBeforeShowForminterceptor has queued for custom rendering — where a phone app would need floating chrome. - System controls over brand theming —
.borderedProminent/.borderedbuttons,.roundedBordertext fields,Formwith.formStyle(.grouped),LabeledContent, andColor.accentColor(the user's own system accent) instead of a hardcoded brand color. Hardcoded pill/capsule iOS theming is the fastest way to look non-native on the Mac. - A menu bar — expose feedback actions as menu commands with keyboard shortcuts:
var body: some Scene {
WindowGroup { ContentView() }
.commands {
CommandMenu("Feedback") {
Button("Send Feedback…") {
Task { try? await Encatch.shared.showForm("feedback-form") }
}
.keyboardShortcut("f", modifiers: [.command, .shift])
}
}
}- Drop soft-keyboard workarounds — keyboard-avoidance scroll hacks are meaningless without an on-screen keyboard. Pointer-driven UIs also favor
Menudropdowns over tap-target chip grids.
Settings / Preferences windows
SwiftUI's Settings { } and Window(_:id:) Scene types are hard-unavailable when compiling for Catalyst — a real compiler error, not a version gate. If you want a Preferences window (Cmd+,), your options are:
- A second
WindowGroup(id:)opened viaopenWindow(id:), withCommandGroup(replacing: .appSettings)binding Cmd+,. This requiresUIApplicationSceneManifest.UIApplicationSupportsMultipleScenes = truein Info.plist — Xcode injects it automatically for SwiftUI-lifecycle apps, but a custom Info.plist path bypasses that, so check yours. - A sidebar destination — fold settings into the sidebar as a regular row and have Cmd+, select it:
.commands {
CommandGroup(replacing: .appSettings) {
Button("Preferences…") { sidebarSelection = .settings }
.keyboardShortcut(",", modifiers: .command)
}
}Other Catalyst limitations to know about
Also verified by building against the SDK under Catalyst:
.menuStyle(.borderedButton)is unavailable under Catalyst — use the default menu style..toolbaritem merging across a switchedNavigationSplitViewdetail view can be unreliable (items silently disappearing on some destinations). If toolbar actions must survive detail-pane switches, render them in a persistent header view above the detail content instead.- None of these affect the SDK itself — they only constrain the host app UI you build around it.
Window sizing and the form overlay
The modal form presents over the topmost view controller of the active window scene, sized to that window — on the Mac that means it overlays your app's window, not the whole screen. Two implications:
- Give your window a sensible floor so forms have room to render:
WindowGroup {
ContentView()
.frame(minWidth: 900, minHeight: 600)
}
.defaultSize(width: 1100, height: 720)(.defaultSize and .windowResizability require a deployment target of iOS 17 / macOS 14 — see the note on targeting below.)
isFullScreen: trueinEncatchConfigmakes the overlay fill the window, not the display. The default (false) card-style presentation generally looks better on desktop.
Inline forms (EncatchInlineFormView) size themselves via onHeightChange exactly as on iOS and work unchanged in Catalyst layouts.
Theme
Encatch.shared.setTheme(_:) themes the SDK's own form content. Mac users notice when the form and the window disagree, so mirror the SDK theme onto your window's appearance:
extension Theme {
var colorScheme: ColorScheme? {
switch self {
case .light: return .light
case .dark: return .dark
case .system: return nil // follow the Mac's own appearance setting
}
}
}
// In your root view:
ContentView()
.preferredColorScheme(currentEncatchTheme.colorScheme)Deployment target
The SDK's floor is iOS 15 / macOS 12, but your Catalyst app can target higher than its dependency's minimum. Targeting iOS 17 gives you Table (sortable columns), .defaultSize/.windowResizability, and the two-parameter .onChange; Mac users tend to run current macOS versions, so a higher floor costs little reach.
Plain macOS (non-Catalyst) targets
In an AppKit or SwiftUI-for-Mac target (no UIKit), the SDK's core compiles and runs:
import Encatch
try await Encatch.shared.initialize(apiKey: "YOUR_API_KEY")
try await Encatch.shared.identifyUser(userName: "user@example.com")
try await Encatch.shared.trackEvent("exported_report")
try await Encatch.shared.trackScreen("EditorWindow")What is not available without UIKit: EncatchFormHost, EncatchInlineFormView, and the WebView-based form rendering — so showForm has no presenter. If you need to collect responses in a plain macOS target, use the onBeforeShowForm interceptor pattern to receive the form's questionnaireFields and render your own AppKit/SwiftUI form, submitting via Encatch.shared.submitForm(_:) — see Build your own form UX on the iOS page. For the hosted form experience, ship a Catalyst target.
Support
- Repository: github.com/get-encatch/encatch-swift
- Issues: github.com/get-encatch/encatch-swift/issues
- iOS reference: iOS SDK — the complete API documentation for this package
Was this page helpful?