Quick Start

Get up and running! ๐Ÿ‘Ÿ๐Ÿ‘Ÿ

Let's get you started! This guide will walk you through the most important steps

Install the SDK ๐Ÿ”ง

Our iOS SDK is distributed as as a SPM package (Swift Package Manager). Simply add a SPM dependency in Xcode on https://github.com/criticalmoments/criticalmoments.

Detailed Steps
  • Open your app project in Xcode. Click "File" > "Add Package Dependencies..."

  • Paste the following URL in the search box in the top right: https://github.com/criticalmoments/criticalmoments and select the Critical Moments package. We suggest leaving the default settings for dependency rules: (Branch=main). We only merge stable releases to our main branch.

  • Select your iOS app project in the "Add to Project..." dropdown

  • Click "Add Package"

  • Select your iOS app target in the "Add to Target" dropdown

  • Click "Add Package"

Create your config file ๐Ÿ“„

Create a JSON config file to your XCode project, starting with a template:

  • Create a copy of of the file cmDevConfig.json template file below, replacing YOUR_BUNDLE_ID with your real bundle ID. This template includes a simple launch message which will help your verify the integration is working.

  • Add the file to your Xcode project. Ensure you check the "Target Membership" box for your app.

  • In Xcode open "Project Settings" > "Build Phases" > "Copy Bundle Resources", and verify the file you just added to the project is listed. If not, add it!

cmDevConfig.json Template
cmDevConfig.json
{
    "configVersion": "v1",
    "appId": "YOUR_BUNDLE_ID",
    "triggers": {
        "namedTriggers": {
            "launchMessageTrigger": {
                "eventName": "app_start",
                "actionName": "launchAlert"
            }
        }
    },
    "actions": {
        "namedActions": {
            "launchAlert": {
                "actionType": "conditional_action",
                "actionData": {
                    "condition": "interface_orientation == 'landscape'",
                    "passedActionName": "landscapeAlert",
                    "failedActionName": "portraitAlert"
                }
            },
            "landscapeAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Launched Landscape",
                    "message": "The app was launched while in landscape orientation."
                }
            },
            "portraitAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Launched Portrait",
                    "message": "The app was launched while in portrait orientation."
                }
            }
        }
    }
}

Integrate the SDK into your app ๐Ÿงฉ

Add the following code snippet into your app.

  • Put it somewhere that will run right after app startup, such as AppDelegate.didFinishLaunchingWithOptions

  • Replace the YOUR_API_KEY string an API key you created in your account.

  • Replace cmDevConfig.json if you changed the name if your config file in the prior step

  • The console will show a warning for now, but this will be resolved in a later step of this guide

In your App Delegate imports add:

import CriticalMoments

Add the following code to your app launch. The location depends on the type of app you have:

  • App Delegate based apps, call from application(_:didFinishLaunchingWithOptions:)

  • SwiftUI lifecycle apps, call from YourApp.init

CriticalMoments.shared().setApiKey("YOUR_API_KEY", error: nil)
CriticalMoments.shared().setDevelopmentConfigName("cmDevConfig.json")
// We will fill this in during a later step. This must be set before releasing to app store
// CriticalMoments.shared().setReleaseConfigUrl("YOUR_URL")
CriticalMoments.shared().start()

Startup work will be dispatched to background threads, and will not impact your app's startup time.

Test the integration ๐Ÿงช

Launch your app! If the integration is working, you should get an alert after startup driven by the template config file. It will tell you if your app was launched in landscape or portrait, and rotating your device/simulator then re-launching should give the opposite result.

The more Critical Moments knows about your user's behaviours, the better you can target and optimize with Critical Moments.

We recommend you log important events, including the important actions your users performs in app. See the events documentation for the suggested events and details.

CriticalMoments.shared().sendEvent("completed_game_level")

Properties are datapoints used in targeting. The more useful data you add, the more powerful the targeting system can be.

We recommend you add the important properties like account_creation_date, has_subscription, and others. See the custom properties documentation for suggested custom properties, and API reference.

try? CriticalMoments.shared().setIntegerProperty(42, forKey: "max_game_level")

Enable Processing For Smart Notifications โฐ

Features like smart notifications require running your app to run in the background for brief periods, to check for ideal conditions. To setup background work:

  1. In Xcode > project editor > Signing & Capabilities > Add (+) > Background Modes check the boxes for "Background fetch" and "Background processing". See Apple guide.

  2. In your Info.plist add an array "Permitted background task scheduler identifiers" (BGTaskSchedulerPermittedIdentifiers) with these two values: io.criticalmoments.bg_fetch and io.criticalmoments.bg_process . See Apple guide.

Setup a cloud config file โ˜๏ธ

For local development you can use your local JSON config file built into the app binary. However, production builds must use a signed config file, hosted on the web.

Read the Remote Control Docs for the best practices of picking a hosting provider, signing, and deploying your config.

Once deployed, set the release config URL in your App Delegate, and uncomment the call to setReleaseConfigUrl("YOUR_URL").

You can test your app using the production config by running a "release" build instead of a "debug" build in XCode.

Update Anytime

Deploying the config file can be done outside of app releases, allowing you to update your apps behaviour without waiting for app reviews, or users to update their builds!

Request Notification Permissions ๐Ÿ“ฌ

If you ever want to show the user notifications, you'll need to ask the user for permission first.

Requesting permission with the Critical Moments helper method instead of the system requestAuthorization method is beneficial as it will schedule any CM notifications after approval. An optional callback will be called after the user approves/denies notifications.

CriticalMoments.shared().requestNotificationPermission()

Update Your Info.plist ๐Ÿ“”

Some optional properties like has_bt_headphones and bluetooth_permission use the system's bluetooth APIs. These won't be called unless you include them in your config. However Apple will detect them, and want a description in the Info.plist file.

Add an entry for NSBluetoothAlwaysUsageDescription to your Info.plist describing your bluetooth usage. If you app uses bluetooth directly, keep the description you have. If you don't otherwise use bluetooth, add a general description, such as "Used to show messages when not using peripherals".

Try a Demo Inside Your Own App ๐Ÿค–

Update your cmDevConfig.json with the config below. Be sure to replace YOUR_APP_ID in the json with your app ID. Each time your app launches, the demo will launch.

This demo is implemented entirely as a CM config file! Feel free to read any part to see how it works. Revert the demo config content when you are done with the demo.

Demo Config File
cmDevConfig.json
{
    "configVersion": "v1",
    "appId": "YOUR_APP_ID",
    "triggers": {
        "namedTriggers": {
            "launchTrigger": {
                "eventName": "app_entered_foreground",
                "actionName": "demoAlert"
            }
        }
    },
    "actions": {
        "namedActions": {
            "demoAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Critcial Moments Demos",
                    "message": "Test Critical Moments features in your own app using this demo config. All features are driven by config, and can be remotely updated without app store updates.",
                    "showOkButton": false,
                    "showCancelButton": true,
                    "style": "large",
                    "customButtons": [
                        {
                            "label": "Conditional Check",
                            "actionName": "conditionalAction"
                        },
                        {
                            "label": "Modal",
                            "actionName": "modalExample"
                        },
                        {
                            "label": "Themed Modal",
                            "actionName": "themedModalExample"
                        },
                        {
                            "label": "Ask For Review",
                            "actionName": "reviewAction"
                        },
                        {
                            "label": "Notification",
                            "actionName": "notificationAction"
                        },
                        {
                            "label": "Banner",
                            "actionName": "banner"
                        },
                        {
                            "label": "Top Banner",
                            "actionName": "topBanner"
                        },
                        {
                            "label": "Themed Banner",
                            "actionName": "themedBanner"
                        },
                        {
                            "label": "Open Web Link",
                            "actionName": "webLinkAction"
                        },
                        {
                            "label": "Embedded Browser",
                            "actionName": "embeddedWebLinkAction"
                        },
                        {
                            "label": "System Alert",
                            "actionName": "basicAlert"
                        }
                    ]
                }
            },
            "reviewAction": {
                "actionType": "review_prompt",
                "actionData": {}
            },
            "conditionalAction": {
                "actionType": "conditional_action",
                "actionData": {
                    "condition": "interface_orientation == 'landscape'",
                    "passedActionName": "landscapeAlert",
                    "failedActionName": "portraitAlert"
                }
            },
            "landscapeAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Landscape",
                    "message": "The app's UI is landscape."
                }
            },
            "portraitAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Portrait",
                    "message": "The app's UI is portrait."
                }
            },
            "basicAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Example Alert",
                    "message": "Buttons, content, actions and targeting can be updated over the air. You can connect any action, such as modals, alerts, web-links and deep-links.",
                    "showCancelButton": true,
                    "okButtonActionName": "nested_alert"
                }
            },
            "banner": {
                "actionType": "banner",
                "actionData": {
                    "preferredPosition": "bottom",
                    "tapActionName": "nested_alert",
                    "body": "Insert banner message"
                }
            },
            "topBanner": {
                "actionType": "banner",
                "actionData": {
                    "preferredPosition": "top",
                    "tapActionName": "nested_alert",
                    "body": "Insert banner message"
                }
            },
            "themedBanner": {
                "actionType": "banner",
                "actionData": {
                    "themeName": "sea",
                    "tapActionName": "nested_alert",
                    "body": "Insert banner message"
                }
            },
            "webLinkAction": {
                "actionType": "link",
                "actionData": {
                    "url": "https://criticalmoments.io"
                }
            },
            "embeddedWebLinkAction": {
                "actionType": "link",
                "actionData": {
                    "url": "https://criticalmoments.io",
                    "useEmbeddedBrowser": true
                }
            },
            "nested_alert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Nested Action",
                    "message": "Your last action was connected to this alert action."
                }
            },
            "modalExample": {
                "actionType": "modal",
                "actionData": {
                    "content": {
                        "pageType": "stack",
                        "pageData": {
                            "sections": [
                                {
                                    "pageSectionType": "image",
                                    "pageSectionData": {
                                        "imageType": "sf_symbol",
                                        "height": 60.0,
                                        "imageData": {
                                            "symbolName": "coloncurrencysign.square.fill",
                                            "weight": "light",
                                            "mode": "hierarchical"
                                        }
                                    }
                                },
                                {
                                    "pageSectionType": "title",
                                    "pageSectionData": {
                                        "title": "Important Announcement"
                                    }
                                },
                                {
                                    "pageSectionType": "title",
                                    "topSpacingScale": 1.5,
                                    "pageSectionData": {
                                        "title": "New pricing coming soon.",
                                        "scaleFactor": 0.64
                                    }
                                },
                                {
                                    "pageSectionType": "body",
                                    "topSpacingScale": 4,
                                    "pageSectionData": {
                                        "bodyText": "Your free plan won't be impacted! However, if you want to upgrade to 'Pro' now is your last chance to lock in our current pricing."
                                    }
                                }
                            ],
                            "buttons": [
                                {
                                    "title": "Got it!",
                                    "style": "large"
                                },
                                {
                                    "title": "More info",
                                    "preventDefault": true,
                                    "style": "info",
                                    "actionName": "embeddedWebLinkAction"
                                }
                            ]
                        }
                    }
                }
            },
            "themedModalExample": {
                "actionType": "modal",
                "actionData": {
                    "themeName": "sea_dark",
                    "content": {
                        "pageType": "stack",
                        "pageData": {
                            "sections": [
                                {
                                    "pageSectionType": "image",
                                    "pageSectionData": {
                                        "imageType": "sf_symbol",
                                        "height": 60.0,
                                        "imageData": {
                                            "symbolName": "coloncurrencysign.square.fill",
                                            "weight": "light",
                                            "mode": "hierarchical"
                                        }
                                    }
                                },
                                {
                                    "pageSectionType": "title",
                                    "pageSectionData": {
                                        "title": "Important Announcement"
                                    }
                                },
                                {
                                    "pageSectionType": "title",
                                    "topSpacingScale": 1.5,
                                    "pageSectionData": {
                                        "title": "New pricing coming soon.",
                                        "scaleFactor": 0.64
                                    }
                                },
                                {
                                    "pageSectionType": "body",
                                    "topSpacingScale": 4,
                                    "pageSectionData": {
                                        "bodyText": "Your free plan won't be impacted! However, if you want to upgrade to 'Pro' now is your last chance to lock in our current pricing."
                                    }
                                }
                            ],
                            "buttons": [
                                {
                                    "title": "Got it!",
                                    "style": "large"
                                },
                                {
                                    "title": "More info",
                                    "preventDefault": true,
                                    "style": "info",
                                    "actionName": "embeddedWebLinkAction"
                                }
                            ]
                        }
                    }
                }
            },
            "notificationAction": {
                "actionType": "conditional_action",
                "actionData": {
                    "condition": "notifications_permission == 'authorized'",
                    "passedActionName": "notificationQueuedAlert",
                    "failedActionName": "permissionIssueAlert"
                }
            },
            "permissionIssueAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Notification Permissions Blocked",
                    "message": "Notifications will not work until your app request's permissions, and the user approves them."
                }
            },
            "notificationQueuedAlert": {
                "actionType": "alert",
                "actionData": {
                    "title": "Swipe away app to see notifications",
                    "message": "Notifications can only appear when the app is in the background.\n\nSwipe away this app in the next 30 seconds to see a notification."
                }
            }
        }
    },
    "notifications": {
        "exampleNotification": {
              "title": "Example Notification",
              "body": "[Sample Content] It only takes 2 minutes to start healthy habbits with LifeTracker.",
              "scheduleCondition": "(eventCount('action:notificationQueuedAlert') != 0 && latestEventTime('action:notificationQueuedAlert') > now() - duration('30s'))",
              "deliveryTime": {
                  "eventName": "app_entered_background",
                  "eventInstance": "latest"
              }
        }
    }
}

Start building ๐Ÿ—๏ธ

Remove the demo actions and triggers from your copy of cmDevConfig.json and start building using the powerful combination of conditionals, events, properties, feature flags, and actions!

Some good places to start and get ideas:

  • Read our Concepts Overview to understand all the tools at your disposal.

  • Try our Demo App for an interactive demo of conditionals, themes, and actions.

Last updated