Skip to main content

Integration Guide for Reachu Swift SDK for Wachanga Team

This guide will walk you through the complete implementation of the Reachu Swift SDK in your iOS application. By the end, you'll have a fully functional ecommerce with shopping cart, checkout, payments, and campaign management.

Table of Contents

  1. Prerequisites
  2. Installation
  3. Initial Configuration
  4. Configuration Files
  5. SDK Initialization
  6. Component Integration
  7. Campaign Management
  8. Design System Configuration
  9. Localization
  10. Verification and Testing

Prerequisites

Before you begin, make sure you have:

  • Xcode 15.0or later
  • iOS 15.0+(iPhone & iPad)
  • Swift 5.9or later
  • Reachu accountwith valid API Key
  • Campaign ID(if using campaign management)

Installation

Step 1: Add the SDK to your project

  1. Open your Xcode project

  2. File → Add Package Dependencies...

  3. Enter the repository URL:

 https://github.com/ReachuDevteam/ReachuSwiftSDK.git

  1. Select the version(use the latest stable release)
  2. Choose your modules: - ReachuUI (required for components)
  • ReachuCore (automatically included)

Step 2: Verify installation

Add this to your main file to verify the imports:

import SwiftUI
import ReachuCore
import ReachuUI
import ReachuDesignSystem

If there are no errors, installation was successful!

Required Modules
  • ReachuCore: Core SDK functionality (always required)
  • ReachuUI: UI components (required for components)
  • ReachuDesignSystem: Design tokens (required for spacing, colors, borders, shadows)

Initial Configuration

Step 1: Create folder structure

Correct File Location

**IMPORTANT:**Configuration files must be inside your project's source code folder, not in the Xcode project root.

In a typical Xcode project, the structure is:

  • Project root: Contains the .xcodeproj and test folders
  • Source code folder: Contains all your Swift app code

Correct structure:

ClientImplementationGuide/ ← Xcode project root
ClientImplementationGuide/ ← Source code folder (where your code goes)
Configuration/ ← Configuration files go HERE
reachu-config.json
reachu-translations.json
ContentView.swift
ClientImplementationGuideApp.swift
...
ClientImplementationGuide.xcodeproj
ClientImplementationGuideTests/
ClientImplementationGuideUITests/

DON'T put files here:

ClientImplementationGuide/ ← Project root
Configuration/ ← INCORRECT (won't work here)
reachu-config.json
reachu-translations.json
...

Put them here:

ClientImplementationGuide/
ClientImplementationGuide/ ← Source code folder
Configuration/ ← CORRECT (inside source code folder)
reachu-config.json
reachu-translations.json

How to identify the correct folder: - It's the folder that contains your App.swift or AppDelegate.swift file

  • It's the folder that appears in Xcode's navigator with the same name as your project
  • It's where you normally save your .swift files

Step 2: Add files to bundle

  1. Create the Configuration folder inside your source code folder(where your App.swift is located)
  2. Create the JSON files (reachu-config.json and reachu-translations.json) inside Configuration
  3. In Xcode, drag the files into your project
  4. **IMPORTANT:**Check "Copy items if needed"
  5. Verify they appear in your app's target
  6. Verify the files are in the correct folder in Xcode's navigator

Configuration Files

reachu-config.json

This is the main configuration file. This example is based on the Pregnancy Demo Appand is fully functional. Copy this template and adjust values according to your needs:

Real Example

This configuration file is based on the Pregnancy Demo Appand is a complete, functional example you can use as a base for your project.

**To use this example:**1. Click the copy button () in the top right corner of the code block 2. Paste the content into your reachu-config.json file 3. **IMPORTANT:**Replace "apiKey": "EW2AVX1-N50MQH4-K14AZK7-X254PBJ" with your own Reachu API Key 4. Adjust campaignId, marketFallback, and other values according to your configuration

reachu-config.json
{
"apiKey": "EW2AVX1-N50MQH4-K14AZK7-X254PBJ",
"campaignId": 14,
"environment": "development",
"theme": {
"name": "Pregnancy Demo Theme",
"mode": "automatic",
"lightColors": {
"primary": "#FF6B35",
"secondary": "#F7931E",
"success": "#34C759",
"warning": "#FF9500",
"error": "#FF3B30",
"info": "#007AFF",
"background": "#F2F2F7",
"surface": "#FFFFFF",
"surfaceSecondary": "#F9F9F9",
"textPrimary": "#000000",
"textSecondary": "#8E8E93",
"textTertiary": "#C7C7CC",
"border": "#E5E5EA",
"borderSecondary": "#D1D1D6",
"priceColor": "#FF6B35"
},
"darkColors": {
"primary": "#FF6B35",
"secondary": "#F7931E",
"success": "#32D74B",
"warning": "#FF9F0A",
"error": "#FF453A",
"info": "#0A84FF",
"background": "#000000",
"surface": "#1C1C1E",
"surfaceSecondary": "#2C2C2E",
"textPrimary": "#FFFFFF",
"textSecondary": "#8E8E93",
"textTertiary": "#48484A",
"border": "#38383A",
"borderSecondary": "#48484A",
"priceColor": "#FF6B35"
},
"borderRadius": {
"small": 4,
"medium": 8,
"large": 12,
"xl": 16,
"circle": 999
}
},
"cart": {
"floatingCartPosition": "bottomRight",
"floatingCartDisplayMode": "iconOnly",
"floatingCartSize": "small",
"autoSaveCart": true,
"showCartNotifications": true,
"enableGuestCheckout": true,
"requirePhoneNumber": false,
"defaultShippingCountry": "DE",
"supportedPaymentMethods": ["stripe", "klarna"]
},
"network": {
"timeout": 30.0,
"retryAttempts": 3,
"enableCaching": true,
"enableLogging": true
},
"ui": {
"enableAnimations": true,
"showProductBrands": true,
"enableHapticFeedback": true,
"shadowConfig": {
"cardShadowRadius": 4,
"cardShadowOpacity": 0.1,
"cardShadowOffset": {
"width": 0,
"height": 2
},
"cardShadowColor": "adaptive",
"buttonShadowEnabled": true,
"buttonShadowRadius": 2,
"buttonShadowOpacity": 0.15,
"modalShadowRadius": 20,
"modalShadowOpacity": 0.3,
"enableBlurEffects": true,
"blurIntensity": 0.3,
"blurStyle": "systemMaterial"
}
},
"productDetail": {
"showNavigationTitle": false,
"imageFullWidth": true,
"imageCornerRadius": 0,
"imageHeight": 400
},
"marketFallback": {
"countryCode": "DE",
"countryName": "Germany",
"currencyCode": "EUR",
"currencySymbol": "€",
"phoneCode": "+49",
"flag": "https://flagcdn.com/w40/de.png"
},
"localization": {
"defaultLanguage": "en",
"fallbackLanguage": "en",
"translationsFile": "reachu-translations"
},
"campaigns": {
"webSocketBaseURL": "https://dev-campaing.reachu.io",
"restAPIBaseURL": "https://dev-campaing.reachu.io"
}
}

Important Fields:

  • apiKey: Your Reachu API Key (get it from the dashboard). Replace the example value with your own API Key - campaignId: Your campaign ID (0 if not using campaigns)
  • environment: "development" for local development, "sandbox" for testing, "production" for production
  • campaigns: URLs for campaign management (only if using campaigns)
  • **theme.lightColors**and theme.darkColors: Color configuration for light and dark modes
  • priceColor: Customizable color for product prices (defaults to primary if not specified). This color is used consistently across all product price displays in cards, detail views, and checkout.
  • theme.borderRadius: Border radius values for all components (optional, defaults provided)
  • ui.shadowConfig: Shadow configuration for cards, buttons, and modals (optional)
  • ui.showProductBrands: Whether to show product brands in cards (default: true)
  • productDetail: Optional configuration for product detail view
  • showNavigationTitle: Whether to show title in navigation bar
  • imageFullWidth: Whether image should take full width
  • imageCornerRadius: Image corner radius
  • imageHeight: Image height in pixels
Important Configuration Notes

**Understanding marketFallback:**The marketFallback configuration is used as a fallbackwhen the SDK cannot get market information from the API. The actual market and shipping country values used in API calls come from the API query (sdk.market.getAvailable()), not from marketFallback.

**How it works:**1. SDK first tries to get markets from API (sdk.market.getAvailable()) 2. If API succeeds, it uses the markets returned by the API 3. If API fails or returns no markets, it uses marketFallback as fallback 4. Products are loaded based on the selected market (from API or fallback)

Important points: - marketFallback doesn't need to match your channel exactly - it will simply show products available for that market

  • cart.defaultShippingCountry should match marketFallback.countryCode for consistency
  • The actual market used comes from the API response, not from marketFallback
  • Products displayed will match the market conditions (country/currency) of the selected market

Verification Checklist: - [ ] marketFallback.countryCode is set to a valid country code (used only if API fails)

  • marketFallback.currencyCode is set to a valid currency code (used only if API fails)
  • cart.defaultShippingCountry matches marketFallback.countryCode for consistency
  • Your channel in Reachu has products available for the markets you want to support
  • Values are in correct format (ISO codes: "DE", "EUR", etc.)

Example configuration:

{
"marketFallback": {
"countryCode": "DE", // ← Used as fallback if API market query fails
"currencyCode": "EUR", // ← Used as fallback if API market query fails
// ...
},
"cart": {
"defaultShippingCountry": "DE", // ← Should match marketFallback.countryCode
// ...
}
}

**Note:**The actual market and shipping country used in API calls come from the API query response (sdk.market.getAvailable()), not from marketFallback. marketFallback is only used when the API is unavailable or fails. Products will be filtered based on the selected market's country and currency.

reachu-translations.json

Translation file for multiple languages. This example includes all necessary translations for German (de) based on the Pregnancy Demo App:

Complete Translations

This file includes all necessary translations for the SDK, including checkout messages, payments, validations, and errors. You can add more languages following the same format.

**To use this example:**1. Click the copy button () in the top right corner of the code block 2. Paste the content into your reachu-translations.json file 3. Add more languages (like "en" or "es") following the same format if needed

reachu-translations.json
{
"de": {
"common.addToCart": "In den Warenkorb",
"common.remove": "Entfernen",
"common.close": "Schließen",
"common.cancel": "Abbrechen",
"common.confirm": "Bestätigen",
"common.continue": "Weiter",
"common.back": "Zurück",
"common.next": "Weiter",
"common.done": "Fertig",
"common.loading": "Laden...",
"common.error": "Fehler",
"common.success": "Erfolg",
"common.retry": "Wiederholen",
"common.apply": "Anwenden",
"common.save": "Speichern",
"common.edit": "Bearbeiten",
"common.delete": "Löschen",
"cart.title": "Warenkorb",
"cart.empty": "Ihr Warenkorb ist leer",
"cart.emptyMessage": "Fügen Sie Produkte hinzu, um mit dem Checkout fortzufahren",
"cart.itemCount": "Artikel",
"cart.items": "Artikel",
"cart.item": "Artikel",
"cart.quantity": "Menge",
"cart.subtotal": "Zwischensumme",
"cart.total": "Gesamt",
"cart.shipping": "Versand",
"cart.tax": "Steuer",
"cart.discount": "Rabatt",
"cart.removeItem": "Artikel entfernen",
"cart.updateQuantity": "Menge aktualisieren",
"checkout.title": "Zur Kasse",
"checkout.proceed": "Zur Kasse gehen",
"checkout.initiatePayment": "Zahlung einleiten",
"checkout.completePurchase": "Kauf abschließen",
"checkout.purchaseComplete": "Kauf abgeschlossen!",
"checkout.purchaseCompleteMessage": "Ihre Bestellung wurde bestätigt. Sie erhalten in Kürze eine E-Mail-Bestätigung.",
"checkout.purchaseCompleteMessageKlarna": "Sie zahlen in 4x zinsfrei. Wir senden Ihnen einige Tage vor jeder Zahlung eine Erinnerung.",
"checkout.paymentFailed": "Zahlung fehlgeschlagen",
"checkout.paymentFailedMessage": "Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut.",
"checkout.tryAgain": "Erneut versuchen",
"checkout.goBack": "Zurück",
"checkout.processingPayment": "Zahlung wird verarbeitet",
"checkout.processingPaymentMessage": "Bitte schließen Sie Ihre Zahlung in Vipps ab...",
"checkout.verifyingPayment": "Zahlung wird überprüft...",
"address.shipping": "Lieferadresse",
"address.billing": "Rechnungsadresse",
"address.firstName": "Vorname",
"address.lastName": "Nachname",
"address.email": "E-Mail",
"address.phone": "Telefon",
"address.address": "Adresse",
"address.city": "Stadt",
"address.state": "Bundesland",
"address.zip": "PLZ",
"address.country": "Land",
"address.phoneColon": "Telefon :",
"payment.method": "Zahlungsmethode",
"payment.selectMethod": "Wählen Sie eine Zahlungsmethode aus, um fortzufahren",
"payment.noMethods": "Keine Zahlungsmethoden verfügbar",
"payment.schedule": "Zahlungsplan",
"payment.downPaymentDueToday": "Anzahlung fällig heute",
"payment.installment": "Rate",
"payment.payNext": "Nächste Zahlung",
"payment.confirmWithKlarna": "Mit Klarna bestätigen",
"payment.cancel": "Abbrechen",
"payment.klarnaCheckout": "Klarna Checkout",
"payment.connectingKlarna": "Verbindung mit Klarna...",
"product.details": "Details",
"product.description": "Beschreibung",
"product.options": "Optionen",
"product.inStock": "Auf Lager",
"product.outOfStock": "Nicht auf Lager",
"product.sku": "SKU",
"product.supplier": "Lieferant",
"product.category": "Kategorie",
"product.stock": "Lagerbestand",
"product.available": "verfügbar",
"product.noImage": "Kein Bild verfügbar",
"order.summary": "Bestellübersicht",
"order.id": "Bestellnummer:",
"order.review": "Bestellung überprüfen",
"order.reviewContent": "Bestellprüfungsinhalt...",
"order.productSummary": "Produktübersicht",
"order.totalForItem": "Gesamt für diesen Artikel:",
"order.colors": "Farben:",
"shipping.options": "Versandoptionen",
"shipping.required": "Erforderlich",
"shipping.noMethods": "Noch keine Versandmethoden für diese Bestellung verfügbar.",
"shipping.calculated": "Der Versand wird für diese Bestellung automatisch berechnet.",
"shipping.total": "Gesamtversand",
"discount.code": "Rabattcode",
"discount.applied": "Rabatt angewendet",
"discount.removed": "Rabatt entfernt",
"discount.invalid": "Ungültiger Rabattcode",
"validation.required": "Dieses Feld ist erforderlich",
"validation.invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"validation.invalidPhone": "Bitte geben Sie eine gültige Telefonnummer ein",
"validation.invalidAddress": "Bitte geben Sie eine vollständige Adresse ein",
"error.network": "Netzwerkfehler. Bitte überprüfen Sie Ihre Verbindung.",
"error.server": "Serverfehler. Bitte versuchen Sie es später erneut.",
"error.unknown": "Ein unbekannter Fehler ist aufgetreten",
"error.tryAgainLater": "Bitte versuchen Sie es später erneut"
}
}

Adding More Languages

To add more languages (for example, English or Spanish), simply add a new key to the JSON object:

{
"de": { /* German translations */ },
"en": { /* English translations */ },
"es": { /* Spanish translations */ }
}

The SDK will automatically detect the language based on the market configured in marketFallback.countryCode.

Default Language

English (en) is the default language. If you don't provide translations, the SDK will use English keys as visible text.

Important Configuration Notes:

  1. environment: Can be "development", "sandbox", or "production"
  • development: For local development
  • sandbox: For testing with test data
  • production: For production
  1. **theme.lightColors.priceColor**and theme.darkColors.priceColor: Customizable color for product prices
  • If not specified, defaults to primary color
  • Used consistently across all product price displays (product cards, detail overlay, checkout)
  • Allows you to customize product price color independently from your primary brand color
  • Example: Set priceColor to "#FF6B35" (orange) while keeping primary as "#007AFF" (blue)
  1. productDetail: Optional configuration to customize the product detail view
  • showNavigationTitle: Controls whether to show title in navigation bar
  • imageFullWidth: If true, image takes full screen width
  • imageCornerRadius: Image corner radius (0 = no rounded corners)
  • imageHeight: Fixed image height in pixels
  1. ui.shadowConfig: Advanced shadow and visual effects configuration
  • cardShadowColor: Can be "adaptive" (adapts to light/dark mode) or a specific color
  • enableBlurEffects: Enables blur effects on modals and overlays
  • blurStyle: Blur style ("systemMaterial", "regularMaterial", etc.)
  • modalShadowRadius and modalShadowOpacity: Control modal shadows
  1. network.enableLogging: Useful for development, but should be false in production for better performance

  2. cart.autoSaveCart: If true, cart is automatically saved to UserDefaults and restored on app restart


SDK Initialization

Step 1: Configure in your main App file

In your App.swift file (or AppDelegate.swift for UIKit apps):

import SwiftUI
import ReachuCore
import ReachuUI
import ReachuDesignSystem

@main
struct YourApp: App {
// MARK: - Global State Managers
// Initialize CartManager and CheckoutDraft once for the entire app
@StateObject private var cartManager = CartManager()
@StateObject private var checkoutDraft = CheckoutDraft()

init() {
// Load configuration from reachu-config.json
// This reads the config file with API key, theme colors, and settings
print(" [YourApp] Loading Reachu SDK configuration...")

// Option 1: Use device locale for country detection
// ConfigurationLoader.loadConfiguration()

// Option 2: Force a specific country (for testing)
ConfigurationLoader.loadConfiguration(userCountryCode: "DE")

print(" [YourApp] Reachu SDK configured successfully")
print(" [YourApp] Theme: \(ReachuConfiguration.shared.theme.name)")
print(" [YourApp] API Key: \(ReachuConfiguration.shared.apiKey.isEmpty ? "Not set" : "\(ReachuConfiguration.shared.apiKey.prefix(8))...")")
print(" [YourApp] Environment: \(ReachuConfiguration.shared.environment)")
}

var body: some Scene {
WindowGroup {
ContentView()
// Inject managers as environment objects
// This makes them available to ALL child views via @EnvironmentObject
.environmentObject(cartManager)
.environmentObject(checkoutDraft)
// Show checkout overlay when user taps checkout button
.sheet(isPresented: $cartManager.isCheckoutPresented) {
RCheckoutOverlay()
.environmentObject(cartManager)
.environmentObject(checkoutDraft)
}
// Global floating cart indicator (optional)
.overlay {
RFloatingCartIndicator()
.environmentObject(cartManager)
}
}
}
}

Step 2: Verify configuration loading

When you run your app, you should see in the console:

 [YourApp] Loading Reachu SDK configuration...
[Config] Found config file: reachu-config.json
[Config] Configuration loaded successfully
[YourApp] Reachu SDK configured successfully
[YourApp] Theme: My App Theme
[YourApp] API Key: YOUR_KEY...
[YourApp] Environment: sandbox

If you see errors, verify:

  • JSON files are in the bundle (check "Copy items if needed" in Xcode)
  • File names are correct (reachu-config.json)
  • JSON is valid (no syntax errors)
  • apiKey is set in the config file
Country Detection

You can use ConfigurationLoader.loadConfiguration() without parameters to auto-detect the country from device locale, or force a specific country with ConfigurationLoader.loadConfiguration(userCountryCode: "DE") for testing.


Component Integration

Step 1: Import required modules in your views

All views that use Reachu components need these imports:

import SwiftUI
import ReachuCore
import ReachuUI
import ReachuDesignSystem // Required for ReachuSpacing, ReachuBorderRadius, etc.

Step 2: Access CartManager via EnvironmentObject

CartManager is already initialized in your App.swift and provided as EnvironmentObject. Access it in your views:

import SwiftUI
import ReachuCore
import ReachuUI
import ReachuDesignSystem

struct ContentView: View {
// Access CartManager from environment
@EnvironmentObject var cartManager: CartManager

var body: some View {
NavigationView {
ScrollView {
VStack(spacing: ReachuSpacing.lg) {
// Your components here
}
}
}
}
}

Design System Tokens

Use ReachuSpacing.md, ReachuSpacing.lg, etc. instead of hardcoded values. These are configurable via reachu-config.json.

Step 3: Add Product Components

Product Banner (Auto-configured from campaign)

The RProductBanner component automatically loads configuration from the active campaign and displays a skeleton loader while loading.

Basic Usage:

import SwiftUI
import ReachuUI
import ReachuDesignSystem

struct MyView: View {
var body: some View {
ScrollView {
VStack(spacing: ReachuSpacing.lg) {
// Automatic product banner (shows skeleton while loading)
RProductBanner()

// With specific component ID (if multiple banners exist)
RProductBanner(componentId: "product-banner-1")

// More components...
}
}
}
}

Parameters: - componentId: String? - Optional component ID to identify a specific banner. If nil, uses the first matching component.

Features: - Skeleton loader while loading

  • Responsive height (uses bannerHeightRatio or falls back to bannerHeight)
  • Dynamic styling from backend (colors, fonts, alignment)
  • Automatic caching for performance
  • Support for multiple banners via componentId
  • Automatic deeplink/CTA link validation with fallback to product detail
  • Optimized font sizes and spacing for better visual hierarchy

The RProductCarousel component supports multiple layouts and automatically loads products from the campaign configuration.

Basic Usage:

// Uses layout from backend config
RProductCarousel()

// With specific component ID (if multiple carousels exist)
RProductCarousel(componentId: "product-carousel-1")

Layout Override (for testing/comparison):

// Force specific layout
RProductCarousel(layout: "full") // Large vertical cards (full width)
RProductCarousel(layout: "compact") // Small vertical cards (2 cards visible)
RProductCarousel(layout: "horizontal") // Horizontal cards (image left, info right)

// Combine componentId and layout
RProductCarousel(componentId: "product-carousel-1", layout: "full")

Parameters: - componentId: String? - Optional component ID to identify a specific carousel. If nil, uses the first matching component.

  • layout: String? - Optional layout override. Options: "full", "compact", "horizontal". If nil, uses layout from backend config.
  • showAddToCartButton: Bool - Whether to show "Add to Cart" button in full layout cards. Default: false.

Features: - Three layout options: full, compact, horizontal

  • Skeleton loader while loading (matches selected layout)
  • Auto-scroll support (configurable interval from backend)
  • Responsive card sizing
  • Automatic loading of all productswhen productIds is empty or missing
  • Pagination indicators (dots) for navigation
  • Support for multiple carousels via componentId
  • Clickable cards open product detail overlay
  • Real-time updates when backend config changes via WebSocket

Product Spotlight (Auto-configured)

The RProductSpotlight component displays a featured product with a hero card layout and optional highlight badge.

Basic Usage:

// Uses hero variant (default)
RProductSpotlight()

// With specific component ID (if multiple spotlights exist)
RProductSpotlight(componentId: "product-spotlight-1")

// With different card variant
RProductSpotlight(variant: .grid) // Grid layout
RProductSpotlight(variant: .list) // List layout
RProductSpotlight(variant: .hero) // Hero layout (default)
RProductSpotlight(variant: .minimal) // Minimal layout

// Without Add to Cart button
RProductSpotlight(showAddToCartButton: false)

// Combine all parameters
RProductSpotlight(
componentId: "product-spotlight-1",
variant: .hero,
showAddToCartButton: true
)

Parameters: - componentId: String? - Optional component ID to identify a specific spotlight. If nil, uses the first matching component.

  • variant: RProductCard.Variant? - Optional card variant override. Options: .grid, .list, .hero, .minimal. If nil, uses .hero (default).
  • showAddToCartButton: Bool - Whether to show the "Add to Cart" button in hero variant. Default: true. Button only shows if product has no variants.

Features: - Hero layout with smaller fonts (optimized for spotlight)

  • Highlight badge with custom text (from backend config)
  • Skeleton loader while loading
  • Conditional "Add to Cart" button (only shows if product has no variants)
  • Clickable card opens product detail overlay
  • Multiple card variants support
  • Support for multiple spotlights via componentId
  • Uses configuration from reachu-config.json (colors, spacing, borders, shadows)

Backend Configuration:

{
"componentId": "product-spotlight-1",
"type": "product_spotlight",
"status": "active",
"customConfig": {
"productId": "408841",
"highlightText": "Feature Product"
}
}

The component automatically:

  • Loads the product by productId
  • Shows highlightText as a badge (if provided)
  • Displays product with hero card layout
  • Shows "Add to Cart" button only if product has no variants

Product Slider (Manual Configuration)

The RProductSlider component requires manual configuration and is useful for displaying specific product collections. Unlike auto-configured components, you need to provide products and callbacks manually.

struct MyView: View {
@EnvironmentObject var cartManager: CartManager

var body: some View {
RProductSlider(
title: "Recommended Products",
layout: .cards,
showSeeAll: false,
onProductTap: { product in
print("Tapped product: \(product.title)")
},
onAddToCart: { product in
Task {
await cartManager.addProduct(product)
}
},
currency: cartManager.currency,
country: cartManager.country
)
.environmentObject(cartManager)
}
}

**Note:**This component requires manual configuration. For auto-configured components, use RProductCarousel, RProductStore, or RProductSpotlight instead.

Product Store (Grid/List)

The RProductStore component displays products in a grid or list layout, automatically configured from the campaign.

Basic Usage:

// Auto-configured from campaign
RProductStore()

// With specific component ID (if multiple stores exist)
RProductStore(componentId: "product-store-1")

Parameters: - componentId: String? - Optional component ID to identify a specific store. If nil, uses the first matching component.

Features: - Grid or list display mode (from backend config)

  • Skeleton loader while loading
  • Automatic fallback to all products if no IDs provided
  • Responsive columns configuration
  • Support for multiple stores via componentId

Step 4: Add Floating Cart Indicator (Optional)

The floating cart indicator is already configured in your App.swift file. If you want to customize it or add it to a specific view:

import SwiftUI
import ReachuUI

struct ContentView: View {
@EnvironmentObject var cartManager: CartManager

var body: some View {
ZStack {
// Your main content
ScrollView {
// ...
}

// Floating cart indicator (optional, already in App.swift)
RFloatingCartIndicator()
.environmentObject(cartManager)
}
}
}

Already Configured

The floating cart indicator and checkout overlay are already configured in your App.swift file. You don't need to add them again unless you want a custom implementation.


Campaign Management

Campaign Configuration

If using campaigns, configure campaignId in reachu-config.json:

{
"apiKey": "YOUR_API_KEY",
"campaignId": 14, // ← Your Campaign ID
"campaigns": {
"webSocketBaseURL": "https://dev-campaing.reachu.io",
"restAPIBaseURL": "https://dev-campaing.reachu.io"
}
}

Backend Data Structure

The SDK uses a two-tier configuration system:

  1. customConfig: Campaign-specific configuration (overrides template defaults)
  2. component.config: Template defaults (used if customConfig is not provided)

Priority:customConfig > component.config

Example GET Response:

{
"components": [
{
"id": 23,
"campaignId": 5,
"componentId": "product_carousel_1",
"status": "active",
"customConfig": {
"productIds": ["408841", "408842", "408843"],
"autoPlay": true,
"interval": 4000,
"layout": "full"
},
"component": {
"id": "product_carousel_1",
"type": "product_carousel",
"name": "Product Carousel",
"config": {
"productIds": [],
"autoPlay": false,
"interval": 3000,
"layout": "full"
}
}
}
]
}

The SDK automatically uses customConfig if available, otherwise falls back to component.config.

Auto-configured Components

When a campaign is active, these components configure automatically:

  • RProductBanner: Featured product banner with customizable styling
  • RProductCarousel: Product carousel with multiple layout options
  • RProductStore: Grid/list product view
  • RProductSpotlight: Featured product spotlight with hero card

You don't need to pass manual configuration - everything comes from the backend when the campaign is active.

Multiple Components of the Same Type

If your backend has multiple components of the same type (e.g., multiple product_carousel components), you can identify them using the componentId parameter:

// First carousel (uses first matching component)
RProductCarousel()

// Specific carousel by componentId
RProductCarousel(componentId: "product-carousel-1")
RProductCarousel(componentId: "product-carousel-2")

// Same for other components
RProductBanner(componentId: "product-banner-1")
RProductStore(componentId: "product-store-1")
RProductSpotlight(componentId: "product-spotlight-1")

How it works: - Each component has a unique componentId in the backend

  • If you don't specify componentId, the SDK uses the first matching component
  • If you specify componentId, the SDK uses that specific component

Example Backend Response:

{
"components": [
{
"componentId": "product-carousel-1",
"type": "product_carousel",
"status": "active",
"config": {
"productIds": ["408841", "408842"],
"autoPlay": true,
"interval": 4000,
"layout": "full"
}
},
{
"componentId": "product-carousel-2",
"type": "product_carousel",
"status": "active",
"config": {
"productIds": [],
"autoPlay": false,
"layout": "compact"
}
}
]
}

Usage Example:

// First carousel (uses first matching component)
RProductCarousel()

// Second carousel (specific component ID)
RProductCarousel(componentId: "product-carousel-2")

This allows you to display multiple instances of the same component type with different configurations. The second carousel in the example above will load all products (empty productIds) while the first loads only specific products.

ProductBanner Styling Properties

The RProductBanner component supports advanced styling configuration from the backend:

Color Properties: - titleColor: Text color for title (hex format: "#FFFFFF")

  • subtitleColor: Text color for subtitle (hex format: "#F0F0F0")
  • buttonBackgroundColor: Background color for CTA button (hex format: "#FF6B6B")
  • buttonTextColor: Text color for CTA button (hex format: "#FFFFFF")
  • backgroundColor: Background overlay color (rgba format: "rgba(0, 0, 0, 0.5)" or hex)

Size Properties: - bannerHeight: Banner height in pixels (150-400, default: 200) - Legacy, use bannerHeightRatio for responsive design - bannerHeightRatio: Banner height as ratio of screen width (0.15-0.6, e.g., 0.25 = 25% of width) - Preferred for responsive design - titleFontSize: Title font size (16-32, default: 24)

  • subtitleFontSize: Subtitle font size (12-20, default: 16)
  • buttonFontSize: Button font size (12-18, default: 14)

Alignment Properties: - textAlignment: Text alignment ("left", "center", "right")

  • contentVerticalAlignment: Vertical content alignment ("top", "center", "bottom")

Other Properties: - overlayOpacity: Overlay opacity (0.0-1.0, default: 0.5)

  • backgroundImageUrl: Background image URL
  • productId: Product ID for navigation
  • title, subtitle, ctaText: Text content
  • ctaLink, deeplink: Navigation links

Example Backend Configuration:

{
"componentId": "product-banner-template",
"type": "product_banner",
"status": "active",
"customConfig": {
"productId": "408841",
"backgroundImageUrl": "https://storage.googleapis.com/banner.jpg",
"title": "Oferta Especial",
"subtitle": "50% de descuento",
"ctaText": "Comprar Ahora",
"titleColor": "#FFFFFF",
"subtitleColor": "#F0F0F0",
"buttonBackgroundColor": "#FF6B6B",
"buttonTextColor": "#FFFFFF",
"backgroundColor": "rgba(0, 0, 0, 0.5)",
"overlayOpacity": 0.3,
"bannerHeightRatio": 0.25,
"titleFontSize": 28,
"subtitleFontSize": 18,
"buttonFontSize": 16,
"textAlignment": "left",
"contentVerticalAlignment": "bottom"
}
}

ProductCarousel Configuration

The RProductCarousel component supports layout configuration from the backend:

Layout Options: - "full": Large vertical cards (full width minus padding, 1.3 aspect ratio)

  • "compact": Small vertical cards (shows 2 cards at once, ~47% width each)
  • "horizontal": Horizontal cards with image left and description right (90% width, 110px height)

Configuration Properties: - productIds: Array of product IDs. Empty array or missing field loads ALL products from channel. - autoPlay: Boolean to enable/disable auto-scroll (default: false if not provided)

  • interval: Auto-scroll interval in milliseconds (default: 3000 if not provided)
  • layout: Layout type ("full", "compact", or "horizontal", default: "full")

**Example Backend Configurations:**Load All Products (No IDs):

{
"componentId": "product-carousel-all",
"type": "product_carousel",
"status": "active",
"config": {
"autoPlay": true,
"interval": 4000,
"layout": "full"
}
}

Specific Products:

{
"componentId": "product-carousel-featured",
"type": "product_carousel",
"status": "active",
"config": {
"productIds": ["408841", "408842", "408843"],
"autoPlay": true,
"interval": 5000,
"layout": "compact"
}
}

Minimal Configuration:

{
"componentId": "product-carousel-simple",
"type": "product_carousel",
"status": "active",
"config": {
"autoPlay": false,
"interval": 3000
}
}

Note: - If productIds is empty or not provided, the component automatically loads all products from the channel using the GetProducts GraphQL query.

  • All configuration properties are optional and have sensible defaults.
  • You can use componentId parameter to display multiple carousels with different configurations.

ProductStore Configuration

The RProductStore component supports grid/list display modes:

Configuration Properties: - mode: "all" (load all products) or "filtered" (use productIds)

  • productIds: Array of product IDs (optional, empty loads all products)
  • displayType: "grid" or "list"
  • columns: Number of columns for grid layout (default: 2)

Example Backend Configuration:

{
"componentId": "product-store-1",
"type": "product_store",
"status": "active",
"customConfig": {
"mode": "filtered",
"productIds": ["408841", "408842", "408843"],
"displayType": "grid",
"columns": 2
}
}

**Note:**If productIds is empty in "filtered" mode, the component automatically falls back to loading all products from the channel.

ProductSpotlight Configuration

The RProductSpotlight component displays a featured product with a hero card layout:

Configuration Properties: - productId: String - Product ID to display

  • highlightText: String? - Optional text to display as a badge/highlight above the card

Example Backend Configuration:

{
"componentId": "product-spotlight-1",
"type": "product_spotlight",
"status": "active",
"customConfig": {
"productId": "408841",
"highlightText": "Feature Product"
}
}

Behavior: - The component loads the product by productId

  • Shows highlightText as a badge above the card (if provided)
  • Uses hero card layout with optimized smaller fonts
  • Shows "Add to Cart" button only if:
  • Product has no variants (variants.isEmpty)
  • Product has stock (quantity > 0)
  • showAddToCartButton parameter is true
  • Card is clickable and opens product detail overlay

Design System Configuration

Border Radius Configuration

All components use border radius values from reachu-config.json:

{
"theme": {
"borderRadius": {
"none": 0,
"small": 4,
"medium": 8,
"large": 12,
"xl": 16,
"circle": 999
}
}
}

Usage in Components: - Cards use ReachuBorderRadius.large or ReachuBorderRadius.xl

  • Buttons use ReachuBorderRadius.medium
  • Badges use ReachuBorderRadius.circle

Shadow Configuration

All components use shadow values from reachu-config.json:

{
"ui": {
"shadowConfig": {
"cardShadowEnabled": true,
"cardShadowRadius": 8,
"cardShadowOffset": { "width": 0, "height": 2 },
"cardShadowOpacity": 0.1,
"buttonShadowEnabled": true,
"buttonShadowRadius": 4,
"buttonShadowOffset": { "width": 0, "height": 1 },
"buttonShadowOpacity": 0.15,
"textShadowEnabled": true,
"textShadowRadius": 2,
"textShadowOffset": { "width": 0, "height": 1 },
"textShadowOpacity": 0.5
}
}
}

Usage in Components: - Cards use .reachuCardShadow(for: colorScheme)

  • Buttons use .reachuButtonShadow(for: colorScheme)
  • Text uses .reachuTextShadow(for: colorScheme)

Spacing Configuration

All components use spacing values from reachu-config.json:

{
"theme": {
"spacing": {
"xs": 4,
"sm": 8,
"md": 16,
"lg": 24,
"xl": 32,
"xxl": 48,
"xxxl": 64
}
}
}

Usage in Components: - Horizontal padding: ReachuSpacing.md

  • Vertical spacing between elements: ReachuSpacing.sm to ReachuSpacing.lg
  • Card spacing in carousels: ReachuSpacing.md (configurable)

**Note:**All design tokens (colors, spacing, border radius, shadows) are centralized in reachu-config.json and automatically applied to all SDK components for consistent styling.


Campaign Management

Campaign States

  • Active Campaign: All components are displayed
  • Paused Campaign: Components are automatically hidden
  • Ended Campaign: Components are automatically hidden
  • Upcoming Campaign: Components are hidden until it starts

Observing Campaign State

import ReachuCore

struct MyView: View {
@ObservedObject private var campaignManager = CampaignManager.shared

var body: some View {
VStack {
if campaignManager.isCampaignActive {
Text("Campaign active")
} else {
Text("No active campaign")
}
}
}
}

WebSocket Events

The SDK automatically handles WebSocket events from the backend:

Component Status Changed:

{
"type": "component_status_changed",
"data": {
"componentId": "product_carousel_1",
"campaignComponentId": 23,
"componentType": "product_carousel",
"status": "active",
"config": {
"productIds": ["408841", "408842", "408843"],
"autoPlay": true,
"interval": 4000,
"layout": "full"
}
}
}

Component Config Updated:

{
"type": "component_config_updated",
"data": {
"componentId": "product_carousel_1",
"campaignComponentId": 23,
"componentType": "product_carousel",
"config": {
"productIds": ["408841", "408842", "408850"],
"autoPlay": false,
"interval": 5000,
"layout": "compact"
}
}
}

**Note:**The SDK supports both new format (with data wrapper) and legacy format (without data wrapper) for backward compatibility.


Localization

Configure Language by Market

The SDK automatically detects language based on the configured country:

  • DE(Germany) → German (de)
  • US(United States) → English (en)
  • ES(Spain) → Spanish (es)
  • And more...

Add Translations

  1. Open reachu-translations.json
  2. Add the language code (e.g., "es", "fr", "it")
  3. Copy all necessary keys
{
"es": {
"common.addToCart": "Añadir al carrito",
"cart.title": "Carrito de compras",
// ... more translations
}
}

Verify Translations

The SDK automatically loads translations based on the market. If a translation is missing, it uses English as fallback.


Verification and Testing

Verification Checklist

Important Configuration (CHECK FIRST)

  • **marketFallback.countryCode**is set to a valid country code (used only if API fails)
  • **marketFallback.currencyCode**is set to a valid currency code (used only if API fails)
  • **cart.defaultShippingCountry**matches marketFallback.countryCode for consistency
  • Your channel in Reachu has products availablefor the markets you want to support
  • Configuration files are in the correct folder(inside source code folder, not project root)
Note on Market Detection

The actual market and shipping country values used in API calls come from the API query response (sdk.market.getAvailable()), not from marketFallback. The marketFallback is only used when the API market query fails or returns no results. Products will be filtered based on the selected market's country and currency conditions.

Configuration

  • reachu-config.json file exists and is in bundle
  • reachu-translations.json file exists and is in bundle
  • apiKey is valid
  • environment is correct (sandbox/production)

Initialization

  • SDK loads without errors
  • Logs show configuration loaded correctly
  • CartManager is available as EnvironmentObject

Components

  • Product Banner displays (if active campaign)
  • Product Banner shows skeleton loader while loading
  • Product Carousel works with correct layout
  • Product Carousel shows skeleton loader while loading
  • Product Carousel auto-scroll works (if enabled)
  • Product Carousel pagination indicators appear
  • Product Store displays products correctly
  • Product Store shows skeleton loader while loading
  • Product Spotlight displays featured product
  • Product Spotlight shows skeleton loader while loading
  • Product Spotlight badge appears (if configured)
  • Product Spotlight "Add to Cart" button works (if product has no variants)
  • Product Slider displays products
  • Multiple components of same type work with different componentId
  • Floating cart indicator appears
  • Checkout opens correctly

Functionality

  • Add products to cart works
  • View cart works
  • Checkout works
  • Payments work (Stripe/Klarna)
  • Translations apply correctly
  1. Market Availability Test - Verify components display when market is available
  • Verify they hide when unavailable
  1. Campaign Test - Activate a campaign from backend
  • Verify auto-configured components appear
  • Pause campaign and verify they hide
  1. Checkout Test - Add products to cart
  • Open checkout
  • Complete payment flow
  • Verify it works correctly

Complete Example

Here's a complete example of a view with all components:

import SwiftUI
import ReachuCore
import ReachuUI
import ReachuDesignSystem

struct HomeView: View {
@EnvironmentObject var cartManager: CartManager

var body: some View {
NavigationView {
ZStack {
ScrollView {
VStack(spacing: ReachuSpacing.lg) {
// Product banner (auto-configured)
// Shows skeleton loader while loading
RProductBanner()
.padding(.top, 20)

// Product carousel (auto-configured)
// Uses layout from backend config
RProductCarousel()

// Product carousel with specific layout (for testing)
// RProductCarousel(layout: "full")
// RProductCarousel(layout: "compact")
// RProductCarousel(layout: "horizontal")

// Product spotlight (auto-configured)
// Shows featured product with hero card
RProductSpotlight()

// Product spotlight with different variant
// RProductSpotlight(variant: .grid)
// RProductSpotlight(variant: .list)

// Recommended products slider (manual configuration)
RProductSlider(
title: "Recommended Products",
layout: .cards,
showSeeAll: false,
onProductTap: { product in
print("Tapped: \(product.title)")
},
onAddToCart: { product in
Task {
await cartManager.addProduct(product)
}
},
currency: cartManager.currency,
country: cartManager.country
)
.environmentObject(cartManager)

// Product store view (auto-configured)
RProductStore()

Spacer(minLength: 100)
}
}

// Floating cart indicator
RFloatingCartIndicator()
.environmentObject(cartManager)
}
.navigationTitle("My Store")
}
}
}


Performance & UX Features

Skeleton Loaders

All campaign components (RProductBanner, RProductCarousel, RProductStore, RProductSpotlight) automatically display skeleton loaders while loading:

  • Product Banner: Shows skeleton with animated shimmer effect
  • Product Carousel: Shows skeleton cards matching the configured layout (full, compact, or horizontal)
  • Product Store: Shows skeleton grid/list items
  • Product Spotlight: Shows skeleton hero card with badge placeholder

No configuration needed - skeletons appear automatically during loading states.

Automatic Caching

The SDK implements intelligent caching to optimize performance:

  • Config Caching: Component configurations are cached and only recalculated when changed
  • Styling Caching: Parsed colors, sizes, and URLs are cached to avoid recalculation
  • Product Caching: Products are cached by the API client
  • Image Caching: Images are cached automatically by SwiftUI's AsyncImage

Responsive Design

Components automatically adapt to different screen sizes:

  • Product Banner: Uses bannerHeightRatio for responsive height (recommended) or falls back to fixed bannerHeight
  • Product Carousel: Card sizes adapt to screen width (75% for full, 85% for compact, 90% for horizontal)
  • Product Store: Grid columns adapt based on screen size

Automatic Fallbacks

Components handle edge cases gracefully:

  • No Product IDs: Automatically loads all products from channel
  • Market Unavailable: Silently hides components (no error shown to user)
  • Config Missing: Uses sensible defaults
  • WebSocket Disconnected: Components continue to work with cached data

Troubleshooting

Problem: SDK doesn't load

Solution: - Verify JSON files are in bundle

  • Verify file names are exact: reachu-config.json
  • Verify files are in the correct location(inside the source code folder, not project root)
  • Check console for parsing errors

Problem: Components don't display (MOST COMMON)

**Solution:**1. Verify configuration: - Check that your channel in Reachu has products available

  • Verify the selected market has products available for that country/currency
  • Ensure marketFallback values are valid (used only if API fails)
  1. Verify files are in the correct location: - They must be inside the source code folder (where your App.swift is)
  • NOT in the Xcode project root
  1. Verify other points: - Verify market is available from API (sdk.market.getAvailable())
  • Verify campaign is active (if using campaigns)
  • Check console logs for errors
  • Check that API queries are returning market information correctly
  • Verify products exist in Reachu for the selected market's country/currency

**Note:**The actual market comes from the API query. Products will be filtered based on the selected market's conditions. If no products match the market conditions, components will be empty.

Problem: Checkout doesn't work or shipping calculation fails

Solution: - Verify cart.defaultShippingCountry matches marketFallback.countryCode

  • Verify your channel in Reachu has shipping methods configured for the country
  • Verify CartManager is set as EnvironmentObject
  • Verify payment methods are configured
  • Check error logs

Problem: Translations don't work

Solution: - Verify reachu-translations.json is in bundle

  • Verify language code matches market
  • Verify all necessary keys are present

Next Steps

Now that you have the SDK configured and working:

  1. Customize Theme: Adjust colors in reachu-config.json
  2. Add More Translations: Complete your translation file
  3. Configure Campaigns: Create campaigns from backend
  4. Optimize UI: Adjust components according to your design

Support

If you need help:


Happy coding!