vakaGitHub

Install Vaka in an iOS app

These steps are written for a coding agent. Do them in order and verify each one before moving on.

Requirements: iOS 15+ (macOS 12+ and tvOS 15+ also work), Swift 5.9+ / Xcode 15+.

1. Add the Swift package

The package is https://github.com/ErikHellman/vaka, product Vaka.

Pick the case that matches the project:

A. The app is built from a Package.swift (or you are adding Vaka to a local package that the app uses). Add the dependency and the product:

dependencies: [
    .package(url: "https://github.com/ErikHellman/vaka", from: "0.1.0"),
],
targets: [
    .target(name: "MyApp", dependencies: [.product(name: "Vaka", package: "vaka")]),
]

If version resolution fails because no release tag exists yet, use branch: "main" instead of from: "0.1.0".

B. The project is generated by XcodeGen (project.yml) or Tuist (Project.swift). Add the package there, not in the .xcodeproj. XcodeGen:

packages:
  Vaka:
    url: https://github.com/ErikHellman/vaka
    from: 0.1.0
targets:
  MyApp:
    dependencies:
      - package: Vaka

Then regenerate the project (xcodegen generate / tuist generate).

C. A plain .xcodeproj. Editing project.pbxproj by hand is error-prone. Ask the human to add it in Xcode: File > Add Package Dependencies…, paste https://github.com/ErikHellman/vaka, and add the Vaka library to the app target. Continue with step 2 while they do; the code will not compile until the package is added.

2. Start the SDK at launch

Start Vaka as early as possible, before other SDKs that install crash handlers.

SwiftUI app (@main struct ...: App):

import SwiftUI
import Vaka

@main
struct MyApp: App {
    init() {
        Vaka.start(Vaka.Options(
            key: "vk_your_ingest_key",
            endpoint: URL(string: "https://vaka.example.com")!,
            environment: {
                #if DEBUG
                "debug"
                #else
                "production"
                #endif
            }()
        ))
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

UIKit app (AppDelegate):

import UIKit
import Vaka

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Vaka.start(Vaka.Options(key: "vk_your_ingest_key", endpoint: URL(string: "https://vaka.example.com")!))
    return true
}

release defaults to CFBundleShortVersionString (the app's MARKETING_VERSION). Leave it, or pass the same string you will use for the dSYM upload in step 4.

The SDK then automatically:

3. Add screen views, events and handled errors

Add these where they fit the app. Do not add user identification unless the human asks for it.

Vaka.screen("Checkout")                         // in .onAppear / viewDidAppear of main screens
Vaka.track("purchase", ["plan": "pro"])         // key product events; values are String, number or Bool
Vaka.addBreadcrumb(category: "cart", message: "Added item 42")

do {
    try checkout()
} catch {
    Vaka.capture(error)                         // handled errors you want to see
}

Vaka.identify("user-123")                       // opt-in only; Vaka.identify(nil) on logout

For SwiftUI, a simple pattern is .onAppear { Vaka.screen("Settings") } on each top-level screen view.

4. Upload dSYMs so crashes get function names, files and lines

Crash reports contain raw addresses. The server symbolicates them with the dSYM uploaded for the same build.

  1. Make sure release builds produce dSYMs: build setting DEBUG_INFORMATION_FORMAT = dwarf-with-dsym for the Release configuration (this is Xcode's default).

  2. Install the CLI on the machine that builds releases (developer Mac or CI): cargo install --git https://github.com/ErikHellman/vaka vaka

  3. Create an API token with write access to the project (admin UI at https://vaka.example.com/ui, or ask the human). Never commit it: put it in CI secrets, or in a git-ignored .vaka.env file next to the .xcodeproj: VAKA_TOKEN=vt_...

  4. Add a Run Script build phase as the last phase of the app target:

    # Upload dSYMs to Vaka (release builds only).
    [ "$CONFIGURATION" = "Release" ] || exit 0
    [ -f "$SRCROOT/.vaka.env" ] && . "$SRCROOT/.vaka.env" && export VAKA_TOKEN
    if ! command -v vaka >/dev/null 2>&1; then
      [ -x "$HOME/.cargo/bin/vaka" ] && PATH="$HOME/.cargo/bin:$PATH" || { echo "warning: vaka CLI not installed; dSYMs not uploaded"; exit 0; }
    fi
    [ -n "$VAKA_TOKEN" ] || { echo "warning: VAKA_TOKEN not set; dSYMs not uploaded"; exit 0; }
    vaka upload dsym --url "https://vaka.example.com" --project "your-project" --release "$MARKETING_VERSION" "$DWARF_DSYM_FOLDER_PATH" \
      || echo "warning: Vaka dSYM upload failed"
    
    • Input file: ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${EXECUTABLE_NAME} (so Xcode runs the phase after the dSYM exists).
    • Xcode 15+ sandboxes build scripts: set ENABLE_USER_SCRIPT_SANDBOXING = NO on the app target, or the script cannot read the dSYM folder and .vaka.env.

    For XcodeGen, add the same script under postBuildScripts with inputFiles.

CI / archives instead of a build phase: after xcodebuild archive, run vaka upload dsym --url "https://vaka.example.com" --project "your-project" --release "<version>" path/to/MyApp.xcarchive/dSYMs.

Without the CLI: upload each DWARF file with curl (send kind before file):

curl -H "Authorization: Bearer $VAKA_TOKEN" \
  -F kind=native -F release="2.3.1" \
  -F file=@MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
  "https://vaka.example.com/api/v1/projects/your-project/artifacts"

release must equal the app's CFBundleShortVersionString (or the release passed to Vaka.Options).

5. Verify

  1. Build and run the app once; confirm it compiles and launches. With debug: true in Vaka.Options, the SDK logs send failures to the console.
  2. Check that data arrives: vaka --url "https://vaka.example.com" --project "your-project" issues (needs VAKA_TOKEN), the MCP tools, or the admin UI. Screen views and events show up in analytics within a few seconds of the next flush.
  3. Optional crash test (run without the debugger attached, because Xcode's debugger catches the crash first): add a temporary button that calls fatalError("Vaka test crash"), tap it, relaunch the app, and look for an EXC_BREAKPOINT issue. Remove the button afterwards.

Notes