diff --git a/README.md b/README.md
index 4d5748d..d4643c9 100644
--- a/README.md
+++ b/README.md
@@ -69,35 +69,52 @@ struct PreLandingView: View {
/* ... */
.onAppear {
Task {
- // 1.- Search for post-install link and proceed if available
- guard let result = try? await traceback.postInstallSearchLink(),
- let tracebackURL = result.url else {
- return
+ do {
+ // 1.- Search for post-install link and proceed if available
+ let result = try await traceback.postInstallSearchLink()
+ if let tracebackURL = result.url {
+ proceed(onOpenURL: tracebackURL)
+ }
+ } catch {
+ // Handle error - network issues, configuration problems, etc.
+ logger.error("Failed to search for post-install link: \(error)")
}
- proceed(onOpenURL: tracebackURL)
}
}
.onOpenURL { url in
proceed(onOpenURL: url)
}
}
-
+
// This method is to be called from onOpenURL or after post install link search
- func proceed(
- onOpenURL: URL
- ) {
- // 2.- Grab the correct url
- // URL is either a post-install link (detected after app download on onAppear above),
- // or an opened url (direct open in installed app)
- guard let linkResult = try? traceback.extractLinkFromURL(url),
- let linkURL = linkResult.url else {
- return assertionFailure("Could not find a valid traceback/universal url in \(url)")
+ func proceed(onOpenURL url: URL) {
+ // 2.- Check if this is a Traceback URL
+ guard traceback.isTracebackURL(url) else {
+ // Not a Traceback URL, handle it elsewhere
+ handleDeepLink(linkURL)
+ return
+ }
+
+ Task {
+ do {
+ // 3.- Check if dynamic campaign link exists (resolves the deep link from the URL)
+ let linkResult = try await traceback.campaignSearchLink(url)
+
+ guard let linkURL = linkResult.url else {
+ // No deep link found in this URL, so we normally continue opening the app Landing screen
+ return
+ }
+
+ // 4.- Handle the url, opening the right content indicated by linkURL
+ // Use linkURL to navigate to the appropriate content in your app
+ // You can also access linkResult.analytics for tracking purposes
+ handleDeepLink(linkURL)
+ sendAnalytics(linkResult.analytics)
+ } catch {
+ // Handle error - network issues, invalid URL, etc.
+ logger.error("Failed to resolve campaign link: \(error)")
+ }
}
-
- // 3.- Handle the url, opening the right content indicated by linkURL
- // Use linkURL to navigate to the appropriate content in your app
- // You can also access linkResult.analytics for tracking purposes
- YOUR CODE HERE
}
}
```
@@ -114,13 +131,17 @@ class YourAppDelegate: NSObject, UIApplicationDelegate {
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
Task {
- // 1.- Trigger a search for installation links
- // if a link is found successfully, it will be sent to proceed(openURL:) below
- guard let result = try? await traceback.postInstallSearchLink(),
- let tracebackURL = result.url else {
- return
+ do {
+ // 1.- Trigger a search for installation links
+ // if a link is found successfully, it will be sent to proceed(openURL:) below
+ let result = try await traceback.postInstallSearchLink()
+ if let tracebackURL = result.url {
+ proceed(onOpenURL: tracebackURL)
+ }
+ } catch {
+ // Handle error - network issues, configuration problems, etc.
+ logger.error("Failed to search for post-install link: \(error)")
}
- proceed(onOpenURL: tracebackURL)
}
return true
}
@@ -135,23 +156,36 @@ class YourAppDelegate: NSObject, UIApplicationDelegate {
proceed(onOpenURL: url)
return true
}
-
+
// This method is to be called from application(open:options:) or after post install link search
- func proceed(
- onOpenURL: URL
- ) {
- // 2.- Grab the correct url
- // URL is either a post-install link (detected after app launch above),
- // or an opened url (direct open in installed app)
- guard let linkResult = try? traceback.extractLinkFromURL(url),
- let linkURL = linkResult.url else {
- return assertionFailure("Could not find a valid traceback/universal url in \(url)")
+ func proceed(onOpenURL url: URL) {
+ // 2.- Check if this is a Traceback URL
+ guard traceback.isTracebackURL(url) else {
+ // Not a Traceback URL, handle it elsewhere
+ handleDeepLink(linkURL)
+ return
+ }
+
+ Task {
+ do {
+ // 3.- Check if dynamic campaign link exists (resolves the deep link from the URL)
+ let linkResult = try await traceback.campaignSearchLink(url)
+
+ guard let linkURL = linkResult.url else {
+ // No deep link found in this URL, so we normally continue opening the app Landing screen
+ return
+ }
+
+ // 4.- Handle the url, opening the right content indicated by linkURL
+ // Use linkURL to navigate to the appropriate content in your app
+ // You can also access linkResult.analytics for tracking purposes
+ handleDeepLink(linkURL)
+ sendAnalytics(linkResult.analytics)
+ } catch {
+ // Handle error - network issues, invalid URL, etc.
+ logger.error("Failed to resolve campaign link: \(error)")
+ }
}
-
- // 3.- Handle the url, opening the right content indicated by linkURL
- // Use linkURL to navigate to the appropriate content in your app
- // You can also access linkResult.analytics for tracking purposes
- YOUR CODE HERE
}
}
```
@@ -191,12 +225,26 @@ The diagnostics will categorize issues as:
## API Reference
+### TracebackSDK Methods
+
+#### `postInstallSearchLink() async throws -> TracebackSDK.Result`
+Searches for the deep link that triggered the app installation. Call this once during app launch.
+
+#### `campaignSearchLink(_ url: URL) async throws -> TracebackSDK.Result`
+Resolves a Traceback URL opened via Universal Link or custom URL scheme into a deep link.
+
+#### `isTracebackURL(_ url: URL) -> Bool`
+Validates if the given URL matches any of the configured Traceback domains.
+
+#### `performDiagnostics()`
+Runs comprehensive validation of your Traceback configuration and outputs diagnostic information.
+
### TracebackSDK.Result
-The result object returned by `postInstallSearchLink()` and `extractLinkFromURL()` contains:
+The result object returned by `postInstallSearchLink()` and `campaignSearchLink()` contains:
- `url: URL?` - The extracted deep link URL to navigate to
-- `matchType: MatchType` - How the link was detected (`.unique`, `.heuristics`, `.ambiguous`, `.intent`, `.none`)
+- `matchType: MatchType` - How the link was detected (`.unique`, `.heuristics`, `.ambiguous`, `.intent`, `.none`, `.unknown`)
- `analytics: [TracebackAnalyticsEvent]` - Analytics events you can send to your preferred platform
### TracebackConfiguration
@@ -220,7 +268,9 @@ public struct TracebackConfiguration {
## Error Handling
-The SDK uses Swift's error handling mechanisms:
+The SDK uses Swift's error handling mechanisms. Both `postInstallSearchLink()` and `campaignSearchLink()` can throw errors:
+
+### Post-Install Link Search
```swift
do {
@@ -228,6 +278,8 @@ do {
if let url = result.url {
// Handle successful link detection
handleDeepLink(url)
+ // Send analytics events
+ sendAnalytics(result.analytics)
} else {
// No link found - normal app startup
handleNormalStartup()
@@ -239,6 +291,26 @@ do {
}
```
+### Campaign Link Resolution
+
+```swift
+do {
+ let result = try await traceback.campaignSearchLink(url)
+ if let deepLink = result.url {
+ // Handle successful link resolution
+ handleDeepLink(deepLink)
+ // Send analytics events
+ sendAnalytics(result.analytics)
+ } else {
+ // URL is valid Traceback URL but no deep link found
+ handleNormalStartup()
+ }
+} catch {
+ // Handle network or configuration errors
+ logger.error("Failed to resolve campaign link: \(error)")
+}
+```
+
## Troubleshooting
### Common Issues
diff --git a/agents.md b/agents.md
new file mode 100644
index 0000000..161e3d3
--- /dev/null
+++ b/agents.md
@@ -0,0 +1,6 @@
+Traceback ios is a companion sdk to communicate with traceback firebase extension
+This sdk is installed via SPM in other projects
+
+README.md shuold give enough onboarding information
+
+don't make authoring header files or commit messages with agent information
diff --git a/e2e/flows/fresh_install.yaml b/e2e/flows/fresh_install.yaml
new file mode 100644
index 0000000..c46a34c
--- /dev/null
+++ b/e2e/flows/fresh_install.yaml
@@ -0,0 +1,9 @@
+# fresh_install.yaml
+
+appId: ${BUNDLE_ID}
+---
+- launchApp
+- tapOn: 'Permitir pegar'
+- assertVisible:
+ text: '.*${DESTINATION_URL}'
+ index: 0
diff --git a/e2e/flows/open_link_in_safari.yaml b/e2e/flows/open_link_in_safari.yaml
new file mode 100644
index 0000000..e30468b
--- /dev/null
+++ b/e2e/flows/open_link_in_safari.yaml
@@ -0,0 +1,10 @@
+# flow.yaml
+
+appId: com.apple.mobilesafari
+---
+- launchApp
+- extendedWaitUntil:
+ visible: "OPEN"
+ timeout: 10000
+- tapOn: "OPEN"
+- tapOn: "OK"
diff --git a/samples/README.md b/samples/README.md
new file mode 100644
index 0000000..70e5e5a
--- /dev/null
+++ b/samples/README.md
@@ -0,0 +1,80 @@
+# Traceback iOS SDK - Sample Applications
+
+This directory contains sample applications demonstrating how to integrate and use the Traceback iOS SDK in different scenarios.
+
+## Available Samples
+
+### [swiftui-basic](./swiftui-basic/)
+A basic SwiftUI application demonstrating:
+- Standard Traceback SDK configuration
+- Post-install link detection
+- Universal Link handling with campaign resolution
+- Deep link navigation
+- Analytics event tracking
+- Diagnostics setup
+
+**Best for:** Getting started with Traceback in a SwiftUI project
+
+### Coming Soon
+
+- **uikit-basic** - Basic UIKit implementation
+- **advanced-analytics** - Advanced analytics integration
+- **custom-domains** - Multiple domain configuration
+- **clipboard-disabled** - Privacy-focused setup without clipboard
+
+## Prerequisites
+
+Before running any sample:
+
+1. **Install the Traceback Firebase Extension** in your Firebase project
+ - Follow instructions at: https://github.com/InQBarna/firebase-traceback-extension
+
+2. **Configure Firebase** for your sample app
+ - Create an iOS app in your Firebase Console
+ - Download `GoogleService-Info.plist`
+
+3. **Set up Associated Domains**
+ - Configure your Apple Developer account
+ - Enable Associated Domains capability
+ - Add the Traceback domain from your Firebase extension
+
+## Running a Sample
+
+Each sample includes its own README with specific setup instructions. Generally:
+
+1. Navigate to the sample directory
+2. Follow the README to configure Firebase settings
+3. Open the `.xcodeproj` or `.xcworkspace` in Xcode
+4. Update the bundle identifier and signing team
+5. Run the app on a physical device (Universal Links don't work in Simulator)
+
+## Testing Deep Links
+
+### Create a Test Link
+
+Use the Traceback Firebase extension to create a test deep link:
+
+```bash
+# Example: Create a link to open /products/123 in your app
+https://your-project-traceback.firebaseapp.com/campaign-name?link=myapp://products/123
+```
+
+### Test Post-Install Flow
+
+1. Copy the Traceback link to clipboard
+2. Delete the app from your device
+3. Install and launch the app
+4. The app should detect and open the deep link
+
+### Test Campaign Links
+
+1. Send yourself the Traceback link via Messages/Email
+2. Open the link with the app already installed
+3. The app should resolve and open the deep link
+
+## Need Help?
+
+- Check the main [README](../README.md) for SDK documentation
+- Review the [Troubleshooting](../README.md#troubleshooting) section
+- Run `traceback.performDiagnostics()` to validate your setup
+- Report issues at: https://github.com/InQBarna/traceback-iOS/issues
diff --git a/samples/e2e-build-and-run.sh b/samples/e2e-build-and-run.sh
new file mode 100755
index 0000000..271a92a
--- /dev/null
+++ b/samples/e2e-build-and-run.sh
@@ -0,0 +1,146 @@
+#!/bin/bash
+
+# Script to build and run the Traceback SwiftUI Sample on iOS Simulator
+# Usage: ./build-and-run.sh [DEVICE_NAME] [PROJECT_PATH]
+#
+# Examples:
+# ./build-and-run.sh "iPhone 15 Pro"
+# ./build-and-run.sh "iPhone 15 Pro" /path/to/TracebackSwiftUIExample.xcodeproj
+# ./build-and-run.sh # Uses defaults
+
+set -e # Exit on error
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Default values
+DEFAULT_DEVICE="iPhone 15 Pro"
+DEFAULT_PROJECT_PATH="swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj"
+SCHEME_NAME="TracebackSwiftUIExample"
+
+# Parse arguments
+DEVICE_NAME="${1:-$DEFAULT_DEVICE}"
+PROJECT_PATH="${2:-$DEFAULT_PROJECT_PATH}"
+
+# Functions
+print_info() {
+ echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+# Validate project exists
+if [ ! -d "$PROJECT_PATH" ]; then
+ print_error "Project not found at: $PROJECT_PATH"
+ exit 1
+fi
+
+print_info "Building Traceback SwiftUI Sample"
+print_info "Device: $DEVICE_NAME"
+print_info "Project: $PROJECT_PATH"
+echo ""
+
+# Get the device UDID
+print_info "Finding simulator..."
+DEVICE_UDID=$(xcrun simctl list devices available | grep "$DEVICE_NAME" | head -n 1 | grep -o -E '\([A-F0-9-]+\)' | tr -d '()')
+
+if [ -z "$DEVICE_UDID" ]; then
+ print_error "Simulator '$DEVICE_NAME' not found"
+ echo ""
+ print_info "Available simulators:"
+ xcrun simctl list devices available | grep iPhone
+ exit 1
+fi
+
+print_info "Found simulator: $DEVICE_NAME ($DEVICE_UDID)"
+
+# Boot simulator if not already running
+print_info "Booting simulator..."
+xcrun simctl boot "$DEVICE_UDID" 2>/dev/null || true
+open -a Simulator
+
+# Wait for simulator to boot
+print_info "Waiting for simulator to boot..."
+while [ "$(xcrun simctl list devices | grep "$DEVICE_UDID" | grep -c "Booted")" -eq 0 ]; do
+ sleep 1
+done
+print_info "Simulator booted"
+
+# Clean build folder (optional, comment out for faster rebuilds)
+print_info "Cleaning build folder..."
+xcodebuild clean \
+ -project "$PROJECT_PATH" \
+ -scheme "$SCHEME_NAME" \
+ -configuration Debug \
+ > /dev/null 2>&1
+
+# Build the project
+print_info "Building project..."
+BUILD_DIR=$(mktemp -d)
+BUILD_LOG="$BUILD_DIR/build.log"
+
+xcodebuild build \
+ -project "$PROJECT_PATH" \
+ -scheme "$SCHEME_NAME" \
+ -configuration Debug \
+ -sdk iphonesimulator \
+ -destination "platform=iOS Simulator,id=$DEVICE_UDID" \
+ -derivedDataPath "$BUILD_DIR" \
+ CODE_SIGNING_ALLOWED=NO \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGN_ENTITLEMENTS="" \
+ > "$BUILD_LOG" 2>&1
+
+BUILD_STATUS=$?
+
+if [ $BUILD_STATUS -ne 0 ]; then
+ print_error "Build failed with status $BUILD_STATUS"
+ echo ""
+ print_info "Build errors:"
+ grep -E "error:" "$BUILD_LOG" | head -20
+ echo ""
+ print_info "Full build log: $BUILD_LOG"
+ exit 1
+fi
+
+print_info "Build succeeded"
+
+# Find the .app bundle
+APP_PATH=$(find "$BUILD_DIR" -name "*.app" -type d | head -n 1)
+
+if [ -z "$APP_PATH" ]; then
+ print_error "Failed to find .app bundle"
+ print_info "Build output in: $BUILD_DIR"
+ exit 1
+fi
+
+print_info "Build succeeded: $APP_PATH"
+
+# Install the app
+print_info "Installing app on simulator..."
+xcrun simctl install "$DEVICE_UDID" "$APP_PATH"
+
+# Get bundle identifier
+BUNDLE_ID=$(defaults read "$APP_PATH/Info.plist" CFBundleIdentifier)
+
+# Cleanup
+rm -rf "$BUILD_DIR"
+
+echo ""
+print_info "✅ Successfully installed $SCHEME_NAME on $DEVICE_NAME"
+print_info "Bundle ID: $BUNDLE_ID"
+echo ""
+print_info "To launch the app, tap its icon in the simulator or run:"
+echo " xcrun simctl launch $DEVICE_UDID $BUNDLE_ID"
+echo ""
+print_warning "Note: Universal Links don't work in Simulator!"
+print_warning "For full testing, deploy to a physical device."
diff --git a/samples/e2e-uninstall.sh b/samples/e2e-uninstall.sh
new file mode 100755
index 0000000..22b61bb
--- /dev/null
+++ b/samples/e2e-uninstall.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+
+# Script to uninstall the Traceback SwiftUI Sample from iOS Simulator
+# Usage: ./uninstall.sh [DEVICE_NAME] [BUNDLE_ID]
+#
+# Examples:
+# ./uninstall.sh "iPhone 15 Pro"
+# ./uninstall.sh "iPhone 15 Pro" com.custom.bundle.id
+# ./uninstall.sh # Uses defaults
+
+set -e # Exit on error
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Default values
+DEFAULT_DEVICE="iPhone 15 Pro"
+DEFAULT_BUNDLE_ID="com.inqbarna.traceback.samples"
+
+# Parse arguments
+DEVICE_NAME="${1:-$DEFAULT_DEVICE}"
+BUNDLE_ID="${2:-$DEFAULT_BUNDLE_ID}"
+
+# Functions
+print_info() {
+ echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+print_info "Uninstalling Traceback SwiftUI Sample"
+print_info "Device: $DEVICE_NAME"
+print_info "Bundle ID: $BUNDLE_ID"
+echo ""
+
+# Get the device UDID
+print_info "Finding simulator..."
+DEVICE_UDID=$(xcrun simctl list devices available | grep "$DEVICE_NAME" | head -n 1 | grep -o -E '\([A-F0-9-]+\)' | tr -d '()')
+
+if [ -z "$DEVICE_UDID" ]; then
+ print_error "Simulator '$DEVICE_NAME' not found"
+ echo ""
+ print_info "Available simulators:"
+ xcrun simctl list devices available | grep iPhone
+ exit 1
+fi
+
+print_info "Found simulator: $DEVICE_NAME ($DEVICE_UDID)"
+
+# Check if app is installed
+print_info "Checking if app is installed..."
+APP_INSTALLED=$(xcrun simctl listapps "$DEVICE_UDID" | grep -c "$BUNDLE_ID" || true)
+
+if [ "$APP_INSTALLED" -eq 0 ]; then
+ print_warning "App with bundle ID '$BUNDLE_ID' is not installed on this simulator"
+ echo ""
+ print_info "Installed apps:"
+ xcrun simctl listapps "$DEVICE_UDID" | grep -E "Bundle|CFBundleIdentifier" | head -20
+ exit 0
+fi
+
+# Uninstall the app
+print_info "Uninstalling app..."
+xcrun simctl uninstall "$DEVICE_UDID" "$BUNDLE_ID"
+
+echo ""
+print_info "✅ Successfully uninstalled app from $DEVICE_NAME"
+print_info "Bundle ID: $BUNDLE_ID"
diff --git a/samples/swiftui-basic/QUICKSTART.md b/samples/swiftui-basic/QUICKSTART.md
new file mode 100644
index 0000000..bc4dc1e
--- /dev/null
+++ b/samples/swiftui-basic/QUICKSTART.md
@@ -0,0 +1,112 @@
+# Quick Start Guide
+
+Get the SwiftUI Basic sample running in 5 minutes.
+
+## Step 1: Open the Project
+
+```bash
+open TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj
+```
+
+## Step 2: Update Configuration
+
+In Xcode, you need to update 3 places with your Firebase Traceback domain:
+
+### 2.1 Update Bundle Identifier & Associated Domains
+
+1. Select `TracebackSwiftUIExample` project in Navigator
+2. Select the `TracebackSwiftUIExample` target
+3. Go to "Signing & Capabilities" tab
+4. **Update your Team** for code signing
+5. **Update Bundle Identifier** (e.g., `com.yourcompany.traceback.demo`)
+6. In "Associated Domains", replace `your-project-traceback.firebaseapp.com` with your actual domain
+
+### 2.2 Update Entitlements File
+
+Open `TracebackSwiftUIExample/TracebackSwiftUIExample.entitlements` and update:
+
+```xml
+applinks:YOUR-ACTUAL-DOMAIN.firebaseapp.com
+```
+
+### 2.3 Update SDK Configuration
+
+Open `TracebackSwiftUIExampleApp.swift` and update line ~29:
+
+```swift
+mainAssociatedHost: URL(string: "https://YOUR-ACTUAL-DOMAIN.firebaseapp.com")!,
+```
+
+### 2.4 Optional: Update URL Scheme
+
+If you want a custom deep link scheme (instead of `myapp://`):
+
+1. Open `Info.plist`
+2. Find `CFBundleURLSchemes`
+3. Change `myapp` to your desired scheme
+
+## Step 3: Build and Run
+
+1. Connect a **physical iOS device** (Universal Links don't work in Simulator)
+2. Select your device in Xcode
+3. Build and Run (Cmd+R)
+4. Check the console for diagnostics output
+
+## Step 4: Test Deep Links
+
+### Test Post-Install Detection
+
+1. Create a test link in your browser:
+ ```
+ https://YOUR-DOMAIN.firebaseapp.com/welcome?link=myapp://home
+ ```
+
+2. **Copy the link** to clipboard (Cmd+C)
+
+3. **Delete the app** from your device
+
+4. **Reinstall and launch** the app
+
+5. The app should detect the clipboard link and navigate to Home
+
+### Test Campaign Links
+
+1. Send yourself this link via Messages:
+ ```
+ https://YOUR-DOMAIN.firebaseapp.com/promo?link=myapp://products/123
+ ```
+
+2. Tap the link with the app already installed
+
+3. The app should open and navigate to Product 123
+
+## Troubleshooting
+
+### "Could not resolve package dependencies"
+
+The project references the Traceback SDK from GitHub. If you're testing locally:
+
+1. In Xcode, go to File → Swift Packages → Resolve Package Versions
+2. Or, update the package reference to point to your local SDK:
+ - File → Swift Packages → Remove Package "Traceback"
+ - File → Add Packages → Add Local → Select your local traceback-iOS folder
+
+### "Universal Links not working"
+
+- Ensure you're testing on a **physical device**
+- Check the Xcode console for diagnostics warnings
+- Verify your Associated Domains are correct
+- Try deleting and reinstalling the app
+
+### "No post-install link detected"
+
+- Make sure `useClipboard: true` in configuration
+- Verify the link was copied before installing
+- Check console logs for detailed information
+
+## Next Steps
+
+- Review the source code to understand the implementation
+- Customize the deep link routes in `DeepLinkRoute.from()`
+- Add your own analytics integration
+- Explore other samples for advanced use cases
diff --git a/samples/swiftui-basic/README.md b/samples/swiftui-basic/README.md
new file mode 100644
index 0000000..2b7a763
--- /dev/null
+++ b/samples/swiftui-basic/README.md
@@ -0,0 +1,295 @@
+# SwiftUI Basic Sample
+
+A basic SwiftUI application demonstrating core Traceback SDK integration with comprehensive debugging.
+
+## Features
+
+- ✅ Traceback SDK configuration
+- ✅ Post-install link detection
+- ✅ Universal Link handling
+- ✅ Campaign link resolution
+- ✅ Analytics event tracking
+- ✅ Diagnostics validation
+- ✅ **SDK Debug UI** - View raw SDK method results in real-time
+
+## Prerequisites
+
+1. **Firebase Project** with Traceback extension installed
+ - Follow: https://github.com/InQBarna/firebase-traceback-extension
+
+2. **Apple Developer Account** with Associated Domains enabled
+
+3. **Physical iOS Device** (Universal Links don't work in Simulator)
+
+## Quick Start with Script
+
+The easiest way to build and install the sample is using the provided build script:
+
+```bash
+cd samples
+./build-and-run.sh "iPhone 15 Pro"
+```
+
+This will:
+1. ✅ Boot the specified simulator
+2. ✅ Build the project
+3. ✅ Install the app
+
+Then you can launch it manually from the simulator home screen.
+
+### Script Usage
+
+**Build and Install:**
+```bash
+cd samples
+
+# Use default device (iPhone 15 Pro) and default project (swiftui-basic)
+./build-and-run.sh
+
+# Specify a device
+./build-and-run.sh "iPhone 14"
+
+# Specify device and custom project path
+./build-and-run.sh "iPhone 15 Pro" swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj
+```
+
+**Uninstall:**
+```bash
+cd samples
+
+# Use default device and bundle ID
+./uninstall.sh
+
+# Specify a device
+./uninstall.sh "iPhone 14"
+
+# Specify device and custom bundle ID
+./uninstall.sh "iPhone 15 Pro" com.custom.bundle.id
+```
+
+**List available simulators:**
+```bash
+xcrun simctl list devices available | grep iPhone
+```
+
+**Note:** Universal Links don't work in the Simulator. For full testing, you need a physical device.
+
+---
+
+## Manual Setup Instructions
+
+### 1. Firebase Configuration
+
+1. Create an iOS app in your Firebase Console
+2. Download `GoogleService-Info.plist` (optional - only if you use Firebase in your app)
+3. Note your Traceback domain from the extension setup (e.g., `your-project-traceback.firebaseapp.com`)
+
+### 2. Update App Configuration
+
+Open `TracebackSwiftUIExample.xcodeproj` in Xcode and:
+
+1. **Update Bundle Identifier**
+ - Select the project in Navigator
+ - Go to "Signing & Capabilities" tab
+ - Change bundle identifier to match your Firebase app
+
+2. **Configure Associated Domains**
+ - In "Signing & Capabilities", ensure "Associated Domains" is enabled
+ - Add your Traceback domain:
+ ```
+ applinks:your-project-traceback.firebaseapp.com
+ ```
+
+3. **Configure URL Schemes**
+ - Go to "Info" tab
+ - Expand "URL Types"
+ - Verify the URL scheme matches your bundle identifier
+ - Or create a custom scheme for deep linking (e.g., `myapp`)
+
+4. **Update Traceback Configuration**
+ - Open `TracebackSwiftUIExampleApp.swift`
+ - Update the `mainAssociatedHost` with your Traceback domain:
+ ```swift
+ let config = TracebackConfiguration(
+ mainAssociatedHost: URL(string: "https://your-project-traceback.firebaseapp.com")!,
+ useClipboard: true,
+ logLevel: .debug
+ )
+ ```
+
+### 3. Run Diagnostics
+
+1. Build and run on a physical device
+2. Check Xcode console for diagnostics output
+3. Resolve any errors or warnings reported
+
+## Testing
+
+### Test Post-Install Detection
+
+1. Create a test link in your Traceback extension:
+ ```
+ https://your-project-traceback.firebaseapp.com/welcome?link=myapp://products/123
+ ```
+
+2. Copy the link to clipboard
+
+3. Delete the app from your device
+
+4. Install and launch the app
+
+5. The app should:
+ - Detect the clipboard link
+ - Show "Post-install link detected!"
+ - Display the deep link: `myapp://products/123`
+
+### Test Campaign Links (Already Installed)
+
+1. Create a campaign link:
+ ```
+ https://your-project-traceback.firebaseapp.com/promo?link=myapp://settings
+ ```
+
+2. Send the link to yourself via Messages or Email
+
+3. Tap the link with the app already installed
+
+4. The app should:
+ - Resolve the campaign link
+ - Show "Campaign link resolved!"
+ - Display the deep link: `myapp://settings`
+
+### Test Universal Links
+
+1. Create a Universal Link and host it on your website or use the Traceback domain
+
+2. Tap the link from Safari, Messages, or Email
+
+3. The app should open and handle the link
+
+## Project Structure
+
+```
+TracebackSwiftUIExample/
+├── TracebackSwiftUIExampleApp.swift # App entry point, SDK setup, and AppState
+├── AppState+DeepLink.swift # Deep link handling methods (extension)
+├── ContentView.swift # Main UI
+└── Info.plist # URL schemes configuration
+```
+
+## Debug UI
+
+The app displays real-time SDK debugging information:
+
+### SDK Results Panel
+- **`isTracebackURL`** - Shows `true`/`false` result for opened URLs
+- **`postInstallSearchLink()`** - Displays the URL returned from post-install detection (or `nil`)
+- **`campaignSearchLink()`** - Displays the URL returned from campaign resolution (or `nil`)
+
+### Debug Status
+- Shows current SDK operation with emoji indicators:
+ - ✅ Success
+ - ❌ Error
+ - ℹ️ Info
+ - ⏳ Loading
+
+### Analytics Events
+- All SDK analytics events are logged in real-time
+- Shows events from both post-install and campaign methods
+
+This makes it easy to:
+- Verify which SDK method returned a link
+- Debug URL validation logic
+- Understand the SDK's behavior step-by-step
+
+## Key Implementation Details
+
+### SDK Initialization
+
+The SDK is initialized in `TracebackSwiftUIExampleApp.swift`:
+
+```swift
+lazy var traceback: TracebackSDK = {
+ let config = TracebackConfiguration(
+ mainAssociatedHost: URL(string: "https://your-project-traceback.firebaseapp.com")!,
+ useClipboard: true,
+ logLevel: .debug
+ )
+ return TracebackSDK.live(config: config)
+}()
+```
+
+### Post-Install Detection
+
+Called once on app launch in `ContentView.onAppear`:
+
+```swift
+.onAppear {
+ Task {
+ do {
+ let result = try await traceback.postInstallSearchLink()
+ if let url = result.url {
+ handleDeepLink(url)
+ sendAnalytics(result.analytics)
+ }
+ } catch {
+ logger.error("Post-install search failed: \(error)")
+ }
+ }
+}
+```
+
+### Universal Link Handling
+
+Handled via `onOpenURL` modifier:
+
+```swift
+.onOpenURL { url in
+ guard traceback.isTracebackURL(url) else { return }
+
+ Task {
+ do {
+ let result = try await traceback.campaignSearchLink(url)
+ if let deepLink = result.url {
+ handleDeepLink(deepLink)
+ sendAnalytics(result.analytics)
+ }
+ } catch {
+ logger.error("Campaign link resolution failed: \(error)")
+ }
+ }
+}
+```
+
+## Troubleshooting
+
+### Universal Links not working
+
+- ✅ Ensure you're testing on a physical device (not Simulator)
+- ✅ Verify Associated Domains are configured correctly
+- ✅ Run `traceback.performDiagnostics()` to check setup
+- ✅ Check that your app is signed with the correct team
+
+### Post-install detection not working
+
+- ✅ Verify `useClipboard: true` in configuration
+- ✅ Ensure the link is copied to clipboard before install
+- ✅ Check console logs for diagnostic information
+
+### Build errors
+
+- ✅ Ensure you're using Xcode 15+ and iOS 15+ deployment target
+- ✅ Verify Traceback SDK package dependency is resolved
+- ✅ Clean build folder (Cmd+Shift+K) and rebuild
+
+## Next Steps
+
+- Review the [main SDK documentation](../../README.md)
+- Explore other samples (UIKit, advanced analytics, etc.)
+- Customize the deep link navigation for your app's needs
+- Integrate with your analytics platform
+
+## Support
+
+- Report issues: https://github.com/InQBarna/traceback-iOS/issues
+- Main documentation: https://github.com/InQBarna/traceback-iOS
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.pbxproj b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..b4f9788
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.pbxproj
@@ -0,0 +1,376 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 56;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1A0001 /* TracebackSwiftUIExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0002 /* TracebackSwiftUIExampleApp.swift */; };
+ 1A0003 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0004 /* ContentView.swift */; };
+ 1A0005 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A0006 /* Assets.xcassets */; };
+ 1A0007 /* Traceback in Frameworks */ = {isa = PBXBuildFile; productRef = 1A0008 /* Traceback */; };
+ 1A0020 /* AppState+DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0021 /* AppState+DeepLink.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 1A0002 /* TracebackSwiftUIExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracebackSwiftUIExampleApp.swift; sourceTree = ""; };
+ 1A0004 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 1A0006 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 1A0009 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 1A000A /* TracebackSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TracebackSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1A000B /* TracebackSwiftUIExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TracebackSwiftUIExample.entitlements; sourceTree = ""; };
+ 1A0021 /* AppState+DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppState+DeepLink.swift"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 1A000C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1A0007 /* Traceback in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 1A000D = {
+ isa = PBXGroup;
+ children = (
+ 1A000E /* TracebackSwiftUIExample */,
+ 1A000F /* Products */,
+ );
+ sourceTree = "";
+ };
+ 1A000E /* TracebackSwiftUIExample */ = {
+ isa = PBXGroup;
+ children = (
+ 1A000B /* TracebackSwiftUIExample.entitlements */,
+ 1A0002 /* TracebackSwiftUIExampleApp.swift */,
+ 1A0021 /* AppState+DeepLink.swift */,
+ 1A0004 /* ContentView.swift */,
+ 1A0006 /* Assets.xcassets */,
+ 1A0009 /* Info.plist */,
+ );
+ path = TracebackSwiftUIExample;
+ sourceTree = "";
+ };
+ 1A000F /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 1A000A /* TracebackSwiftUIExample.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 1A0010 /* TracebackSwiftUIExample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1A0011 /* Build configuration list for PBXNativeTarget "TracebackSwiftUIExample" */;
+ buildPhases = (
+ 1A0012 /* Sources */,
+ 1A000C /* Frameworks */,
+ 1A0013 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = TracebackSwiftUIExample;
+ packageProductDependencies = (
+ 1A0008 /* Traceback */,
+ );
+ productName = TracebackSwiftUIExample;
+ productReference = 1A000A /* TracebackSwiftUIExample.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 1A0014 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1500;
+ LastUpgradeCheck = 1500;
+ TargetAttributes = {
+ 1A0010 = {
+ CreatedOnToolsVersion = 15.0;
+ };
+ };
+ };
+ buildConfigurationList = 1A0015 /* Build configuration list for PBXProject "TracebackSwiftUIExample" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 1A000D;
+ packageReferences = (
+ 1A0016 /* XCRemoteSwiftPackageReference "traceback-iOS" */,
+ );
+ productRefGroup = 1A000F /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 1A0010 /* TracebackSwiftUIExample */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 1A0013 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1A0005 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 1A0012 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1A0003 /* ContentView.swift in Sources */,
+ 1A0020 /* AppState+DeepLink.swift in Sources */,
+ 1A0001 /* TracebackSwiftUIExampleApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1A0017 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 1A0018 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 1A0019 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = TracebackSwiftUIExample/TracebackSwiftUIExample.entitlements;
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = TracebackSwiftUIExample/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.inqbarna.traceback.samples;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 1A001A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = TracebackSwiftUIExample/TracebackSwiftUIExample.entitlements;
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = TracebackSwiftUIExample/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.inqbarna.traceback.samples;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1A0011 /* Build configuration list for PBXNativeTarget "TracebackSwiftUIExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1A0019 /* Debug */,
+ 1A001A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1A0015 /* Build configuration list for PBXProject "TracebackSwiftUIExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1A0017 /* Debug */,
+ 1A0018 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 1A0016 /* XCRemoteSwiftPackageReference "traceback-iOS" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/InQBarna/traceback-iOS";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 0.5.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 1A0008 /* Traceback */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 1A0016 /* XCRemoteSwiftPackageReference "traceback-iOS" */;
+ productName = Traceback;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 1A0014 /* Project object */;
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..bf273b6
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+ "originHash" : "da14aa4c0ceb4370e18b620abeb611396c07bac1e2daf7ae45770f9925eb9b43",
+ "pins" : [
+ {
+ "identity" : "traceback-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/InQBarna/traceback-iOS",
+ "state" : {
+ "revision" : "948d0ef95b2373867b018120841d662fb75a81f4",
+ "version" : "0.5.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/AppState+DeepLink.swift b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/AppState+DeepLink.swift
new file mode 100644
index 0000000..57633a7
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/AppState+DeepLink.swift
@@ -0,0 +1,66 @@
+//
+// AppState+DeepLink.swift
+// TracebackSwiftUIExample
+//
+// Deep link handling methods for AppState
+//
+
+import Foundation
+
+extension AppState {
+
+ /// Checks for post-install link on app launch
+ /// Should be called only once when the app first appears
+ func checkPostInstallLink() async {
+ debugMessage = "Checking for post-install link..."
+
+ do {
+ let result = try await traceback.postInstallSearchLink()
+
+ if let result, let url = result.url {
+ debugMessage = "✅ Post-install link detected!"
+ handlePostInstallLink(url)
+ sendAnalytics(result.analytics)
+ } else {
+ debugMessage = "No post-install link found"
+ }
+ } catch {
+ debugMessage = "❌ Post-install check failed: \(error.localizedDescription)"
+ print("[Error] Post-install search failed: \(error)")
+ }
+ }
+
+ /// Handles a URL opened via Universal Link or custom scheme
+ /// Resolves campaign links via the Traceback SDK
+ func handleOpenURL(_ url: URL) async {
+ print("[URL Received] \(url.absoluteString)")
+
+ // Check if this is a Traceback URL and update the debug flag
+ let isTraceback = traceback.isTracebackURL(url)
+ isTracebackURL = isTraceback
+
+ guard isTraceback else {
+ print("[isTracebackURL] false - ignoring")
+ debugMessage = "❌ Not a Traceback URL: \(url.host ?? "unknown")"
+ return
+ }
+
+ print("[isTracebackURL] true - resolving campaign")
+ debugMessage = "⏳ Resolving campaign link..."
+
+ do {
+ let result = try await traceback.campaignSearchLink(url)
+
+ if let result, let deepLink = result.url {
+ debugMessage = "✅ Campaign link resolved!"
+ handleCampaignLink(deepLink)
+ sendAnalytics(result.analytics)
+ } else {
+ debugMessage = "ℹ️ No deep link in campaign URL"
+ }
+ } catch {
+ debugMessage = "❌ Campaign resolution failed: \(error.localizedDescription)"
+ print("[Error] Campaign link resolution failed: \(error)")
+ }
+ }
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..13613e3
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/Contents.json b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/ContentView.swift b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/ContentView.swift
new file mode 100644
index 0000000..e2b8a59
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/ContentView.swift
@@ -0,0 +1,214 @@
+//
+// ContentView.swift
+// TracebackSwiftUIExample
+//
+// Main view demonstrating Traceback SDK integration
+//
+
+import SwiftUI
+import Traceback
+
+struct ContentView: View {
+ @EnvironmentObject var appState: AppState
+
+ var body: some View {
+ ScrollView {
+ VStack(spacing: 20) {
+ Text("Debug Status")
+ .font(.headline)
+
+ // Display debug message directly in body (not in statusSection) for Maestro visibility
+ Text(appState.debugMessage)
+ .font(.body)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+
+ // Current route display
+ currentRouteSection
+
+ // Analytics events
+ analyticsSection
+
+ Spacer()
+
+ // Instructions
+ instructionsSection
+ }
+ .padding()
+ }
+ .onAppear {
+ Task {
+ await appState.checkPostInstallLink()
+ }
+ }
+ .onOpenURL { url in
+ Task {
+ await appState.handleOpenURL(url)
+ }
+ }
+ }
+
+ // MARK: - View Components
+
+ private var statusSection: some View {
+ VStack(spacing: 8) {
+ Text(verbatim: appState.debugMessage)
+ .font(.body)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ }
+ .padding()
+ .frame(maxWidth: .infinity)
+ .background(Color.blue.opacity(0.1))
+ .cornerRadius(10)
+ }
+
+ private var currentRouteSection: some View {
+ VStack(spacing: 12) {
+ Text("SDK Results")
+ .font(.headline)
+
+ // isTracebackURL result
+ HStack {
+ Text("isTracebackURL:")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ Spacer()
+ if let isTraceback = appState.isTracebackURL {
+ Text(isTraceback ? "✅ true" : "❌ false")
+ .font(.caption)
+ .fontWeight(.semibold)
+ .foregroundColor(isTraceback ? .green : .red)
+ } else {
+ Text("—")
+ .font(.caption)
+ .foregroundColor(.gray)
+ }
+ }
+
+ Divider()
+
+ // Post-install link result
+ HStack(alignment: .center, spacing: 4) {
+ Text("postInstallSearchLink():")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ Spacer()
+ if let postInstall = appState.postInstallLink {
+ Text(postInstall.absoluteString)
+ .font(.caption2)
+ .foregroundColor(.primary)
+ .multilineTextAlignment(.leading)
+ } else {
+ Text("nil")
+ .font(.caption2)
+ .foregroundColor(.gray)
+ }
+ }
+
+ Divider()
+
+ // Campaign link result
+ HStack(alignment: .center, spacing: 4) {
+ Text("campaignSearchLink():")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ Spacer()
+ if let campaign = appState.campaignSearchLink {
+ Text(campaign.absoluteString)
+ .font(.caption2)
+ .foregroundColor(.primary)
+ .multilineTextAlignment(.leading)
+ } else {
+ Text("nil")
+ .font(.caption2)
+ .foregroundColor(.gray)
+ }
+ }
+ }
+ .padding()
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(Color.green.opacity(0.1))
+ .cornerRadius(10)
+ }
+
+ private var analyticsSection: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Analytics Events")
+ .font(.headline)
+
+ if appState.analyticsEvents.isEmpty {
+ Text("No events yet")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ } else {
+ ScrollView {
+ VStack(alignment: .leading, spacing: 4) {
+ ForEach(appState.analyticsEvents.indices, id: \.self) { index in
+ Text("• \(appState.analyticsEvents[index])")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ }
+ }
+ }
+ .frame(maxHeight: 100)
+ }
+ }
+ .padding()
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(Color.orange.opacity(0.1))
+ .cornerRadius(10)
+ }
+
+ private var instructionsSection: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ Text("How to Test")
+ .font(.headline)
+
+ VStack(alignment: .leading, spacing: 8) {
+ instructionRow(
+ icon: "doc.on.clipboard",
+ title: "Post-Install",
+ description: "Copy Traceback link, delete app, reinstall"
+ )
+
+ instructionRow(
+ icon: "link",
+ title: "Campaign",
+ description: "Tap Traceback link from Messages/Email"
+ )
+ }
+ }
+ .padding()
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(Color.gray.opacity(0.1))
+ .cornerRadius(10)
+ }
+
+ private func instructionRow(icon: String, title: String, description: String) -> some View {
+ HStack(alignment: .top, spacing: 12) {
+ Image(systemName: icon)
+ .foregroundColor(.blue)
+ .frame(width: 24)
+
+ VStack(alignment: .leading, spacing: 2) {
+ Text(title)
+ .font(.subheadline)
+ .fontWeight(.semibold)
+ Text(description)
+ .font(.caption)
+ .foregroundColor(.secondary)
+ }
+ }
+ }
+
+}
+
+// MARK: - Preview
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ .environmentObject(AppState())
+ }
+}
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Info.plist b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Info.plist
new file mode 100644
index 0000000..0ea26c4
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/Info.plist
@@ -0,0 +1,35 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ com.example.traceback.swiftui
+ CFBundleURLSchemes
+
+ myapp
+
+
+
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+
+
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/TracebackSwiftUIExample.entitlements b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/TracebackSwiftUIExample.entitlements
new file mode 100644
index 0000000..e6c760c
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/TracebackSwiftUIExample.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.associated-domains
+
+ applinks:traceback-extension-samples-traceback.web.app
+
+
+
diff --git a/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/TracebackSwiftUIExampleApp.swift b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/TracebackSwiftUIExampleApp.swift
new file mode 100644
index 0000000..b74737c
--- /dev/null
+++ b/samples/swiftui-basic/TracebackSwiftUIExample/TracebackSwiftUIExample/TracebackSwiftUIExampleApp.swift
@@ -0,0 +1,86 @@
+//
+// TracebackSwiftUIExampleApp.swift
+// TracebackSwiftUIExample
+//
+// A basic SwiftUI app demonstrating Traceback SDK integration
+//
+
+import SwiftUI
+import Traceback
+
+@main
+struct TracebackSwiftUIExampleApp: App {
+ @StateObject private var appState = AppState()
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ .environmentObject(appState)
+ }
+ }
+}
+
+// MARK: - App State
+
+@MainActor
+class AppState: ObservableObject {
+ // SDK Debug Information
+ @Published var postInstallLink: URL?
+ @Published var campaignSearchLink: URL?
+ @Published var isTracebackURL: Bool?
+ @Published var debugMessage: String = "Waiting for links..."
+ @Published var analyticsEvents: [String] = []
+
+ lazy var traceback: TracebackSDK = {
+ let config = TracebackConfiguration(
+ mainAssociatedHost: URL(string: "https://traceback-extension-samples-traceback.web.app")!,
+ useClipboard: true,
+ logLevel: .debug
+ )
+ return TracebackSDK.live(config: config)
+ }()
+
+ init() {
+ // Run diagnostics on init (only in debug builds)
+ #if DEBUG
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
+ self?.traceback.performDiagnostics()
+ }
+ #endif
+ }
+
+ func handlePostInstallLink(_ url: URL) {
+ postInstallLink = url
+ debugMessage = "Post-install link: \(url.absoluteString)"
+ print("[Post-Install] \(url.absoluteString)")
+ }
+
+ func handleCampaignLink(_ url: URL) {
+ campaignSearchLink = url
+ debugMessage = "Campaign link: \(url.absoluteString)"
+ print("[Campaign] \(url.absoluteString)")
+ }
+
+ func sendAnalytics(_ events: [TracebackAnalyticsEvent]) {
+ for event in events {
+ let eventDescription = formatAnalyticsEvent(event)
+ analyticsEvents.append(eventDescription)
+ print("[Analytics] \(eventDescription)")
+ }
+ }
+
+ private func formatAnalyticsEvent(_ event: TracebackAnalyticsEvent) -> String {
+ switch event {
+ case .postInstallDetected(let url):
+ return "Post-install detected: \(url.absoluteString)"
+ case .postInstallError(let error):
+ return "Post-install error: \(error.localizedDescription)"
+ case .campaignResolved(let url):
+ return "Campaign resolved: \(url.absoluteString)"
+ case .campaignResolvedLocally(let url):
+ return "Campaign resolved locally: \(url.absoluteString)"
+ case .campaignError(let error):
+ return "Campaign error: \(error.localizedDescription)"
+ }
+ }
+}