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
- Prerequisites
- Installation
- Initial Configuration
- Configuration Files
- SDK Initialization
- Component Integration
- Campaign Management
- Design System Configuration
- Localization
- 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
-
Open your Xcode project
-
File → Add Package Dependencies...
-
Enter the repository URL:
https://github.com/ReachuDevteam/ReachuSwiftSDK.git
- Select the version(use the latest stable release)
- 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!
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
**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
.xcodeprojand 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
.swiftfiles
Step 2: Add files to bundle
- Create the
Configurationfolder inside your source code folder(where yourApp.swiftis located) - Create the JSON files (
reachu-config.jsonandreachu-translations.json) insideConfiguration - In Xcode, drag the files into your project
- **IMPORTANT:**Check "Copy items if needed"
- Verify they appear in your app's target
- 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:
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
{
"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 productioncampaigns: URLs for campaign management (only if using campaigns)- **
theme.lightColors**andtheme.darkColors: Color configuration for light and dark modes priceColor: Customizable color for product prices (defaults toprimaryif 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 viewshowNavigationTitle: Whether to show title in navigation barimageFullWidth: Whether image should take full widthimageCornerRadius: Image corner radiusimageHeight: Image height in pixels
**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.defaultShippingCountryshould matchmarketFallback.countryCodefor 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.currencyCodeis set to a valid currency code (used only if API fails) -
cart.defaultShippingCountrymatchesmarketFallback.countryCodefor 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:
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
{
"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"
}
}
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.
English (en) is the default language. If you don't provide translations, the SDK will use English keys as visible text.
Important Configuration Notes:
environment: Can be"development","sandbox", or"production"
development: For local developmentsandbox: For testing with test dataproduction: For production
- **
theme.lightColors.priceColor**andtheme.darkColors.priceColor: Customizable color for product prices
- If not specified, defaults to
primarycolor - 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
priceColorto"#FF6B35"(orange) while keepingprimaryas"#007AFF"(blue)
productDetail: Optional configuration to customize the product detail view
showNavigationTitle: Controls whether to show title in navigation barimageFullWidth: Iftrue, image takes full screen widthimageCornerRadius: Image corner radius (0 = no rounded corners)imageHeight: Fixed image height in pixels
ui.shadowConfig: Advanced shadow and visual effects configuration
cardShadowColor: Can be"adaptive"(adapts to light/dark mode) or a specific colorenableBlurEffects: Enables blur effects on modals and overlaysblurStyle: Blur style ("systemMaterial","regularMaterial", etc.)modalShadowRadiusandmodalShadowOpacity: Control modal shadows
-
network.enableLogging: Useful for development, but should befalsein production for better performance -
cart.autoSaveCart: Iftrue, cart is automatically saved toUserDefaultsand 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)
apiKeyis set in the config file
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
}
}
}
}
}
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
bannerHeightRatioor falls back tobannerHeight) - 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
Product Carousel (Auto-configured)
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". Ifnil, 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
productIdsis 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. Ifnil, 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
highlightTextas 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)
}
}
}
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:
customConfig: Campaign-specific configuration (overrides template defaults)component.config: Template defaults (used ifcustomConfigis 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 stylingRProductCarousel: Product carousel with multiple layout optionsRProductStore: Grid/list product viewRProductSpotlight: 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 URLproductId: Product ID for navigationtitle,subtitle,ctaText: Text contentctaLink,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:3000if 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
componentIdparameter 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
highlightTextas 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) showAddToCartButtonparameter istrue- 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.smtoReachuSpacing.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
- Open
reachu-translations.json - Add the language code (e.g.,
"es","fr","it") - 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**matchesmarketFallback.countryCodefor 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)
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.jsonfile exists and is in bundle -
reachu-translations.jsonfile exists and is in bundle -
apiKeyis valid -
environmentis correct (sandbox/production)
Initialization
- SDK loads without errors
- Logs show configuration loaded correctly
-
CartManageris available asEnvironmentObject
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
Recommended Tests
- Market Availability Test - Verify components display when market is available
- Verify they hide when unavailable
- Campaign Test - Activate a campaign from backend
- Verify auto-configured components appear
- Pause campaign and verify they hide
- 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, orhorizontal) - 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
bannerHeightRatiofor responsive height (recommended) or falls back to fixedbannerHeight - 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
marketFallbackvalues are valid (used only if API fails)
- Verify files are in the correct location: - They must be inside the source code folder (where your
App.swiftis)
- NOT in the Xcode project root
- 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
CartManageris set asEnvironmentObject - 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:
- Customize Theme: Adjust colors in
reachu-config.json - Add More Translations: Complete your translation file
- Configure Campaigns: Create campaigns from backend
- Optimize UI: Adjust components according to your design
Support
If you need help:
- Email: support@reachu.io
- Documentation: docs.reachu.io
- Issues: GitHub Issues
Happy coding!