Quickstart: iOS
This quickstart adds the Grona iOS SDK to your app, starts it when the app launches, and shows Grona content in the home_top placement on your home screen. At the end you check the result in the SDK’s debug view. It takes one Swift package, one host type, one call at launch and one view on the screen.
The iOS SDK is at version 0.1.0. It supports iOS 16 and later, and needs Swift 5.10 or later.
Before you start
Section titled “Before you start”Grona sets up your workspace for apps and gives you these values. The examples on this page use Acme Market’s.
| Value | Example |
|---|---|
| Workspace key | acme-market |
| Config URL | https://api.grona.ai/v1/mobile/workspaces/acme-market/config |
| Events URL | https://api.grona.ai/v1/mobile/events |
| Preview pairing URL | https://api.grona.ai/v1/mobile/preview/pair |
| Configuration key id and public key | acme-market-2026-10 and a base64 public key |
| Signed bundled configuration | A grona-config.json file |
Add the SDK
Section titled “Add the SDK”-
Add the package. In Xcode, choose File > Add Package Dependencies, enter
https://github.com/GronaAI/grona-ios, and set Dependency Rule to Exact Version0.1.0. Add theGronaandGronaDebugproducts to your app target. For aPackage.swiftsetup and the requirements, see Install the SDK. -
Add the bundled configuration. Drag the signed
grona-config.jsonfrom Grona into your project and tick your app target. The SDK applies it at launch and when the device is offline, before the first download from the config URL.
Initialise the SDK
Section titled “Initialise the SDK”-
Implement
GronaHost. The SDK calls it on the main actor to look up products, add them to the basket, replace a basket line and open screens. This minimal version knows no products and accepts no actions, which is enough for a first banner or message without a call to action:AcmeGronaHost.swift import Grona// A minimal host. The full version in Handle actions takes your app's services.@MainActorfinal class AcmeGronaHost: GronaHost {func product(id: String) -> GronaProduct? { nil }func add(_ lines: [GronaLine], source: GronaSource) -> GronaAddResult { .rejected }func replace(productID: String, with line: GronaLine, source: GronaSource) -> GronaAddResult { .rejected }func navigate(to route: GronaRoute, source: GronaSource) -> Bool { false }}Content that names products or adds them to the basket shows nothing until
product(id:)returns your products from a fast in-memory lookup andaddadds them. See Handle actions. -
Call
Grona.startat launch. In a SwiftUI app, call it from yourAppinitialiser with the values from Grona. For an app delegate, see Initialise the SDK.AcmeMarketApp.swift import Gronaimport SwiftUI@mainstruct AcmeMarketApp: App {// Base64 DER SubjectPublicKeyInfo from Grona; a public key, not a secretprivate static let configPublicKey = "REPLACE_WITH_CONFIG_PUBLIC_KEY_FROM_GRONA"init() {do {let options = try GronaOptions(workspaceKey: "acme-market",configURL: URL(string: "https://api.grona.ai/v1/mobile/workspaces/acme-market/config"),bundledConfig: Bundle.main.url(forResource: "grona-config", withExtension: "json"),configKeys: ["acme-market-2026-10": Self.configPublicKey],eventsURL: URL(string: "https://api.grona.ai/v1/mobile/events"),previewURL: URL(string: "https://api.grona.ai/v1/mobile/preview/pair"))Grona.start(options: options, host: AcmeGronaHost())} catch {assertionFailure("Invalid Grona options: \(error)")}}var body: some Scene {WindowGroup {HomeView()}}}The
GronaOptionsinitialiser throwsGronaOptionsErrorif there is no configuration key, if a URL is nothttps, or if the workspace key is not valid. After that, the SDK never throws into your app.Grona.startreturns at once and does its work off the main thread. It is ignored on repeat calls and in app extensions. -
Pass the shopper’s consent answer. Consent starts as
.unknownon every launch: events stay in memory and no events are sent. CallsetConsentafterstartwith the answer from your own consent flow, and again whenever it changes:import Gronafunc analyticsConsentChanged(_ allowed: Bool?) {guard let allowed else {Grona.setConsent(.unknown)return}Grona.setConsent(allowed ? .granted : .denied)}Consent controls events, not rendering: Grona content still shows under
.denied, and nothing is collected. See Consent.
Show the home_top placement
Section titled “Show the home_top placement”-
Add a slot where the content should appear, with the placement
home_topand the home screen:HomeView.swift import Gronaimport SwiftUIstruct HomeView: View {var body: some View {ScrollView {VStack(spacing: 0) {GronaSlot(placement: "home_top", screen: .home)// The rest of your home screen}}}}HomeViewController.swift import Gronaimport UIKitfinal class HomeViewController: UIViewController {private let homeTopSlot = GronaSlotView(placement: "home_top")override func viewDidLoad() {super.viewDidLoad()homeTopSlot.translatesAutoresizingMaskIntoConstraints = falseview.addSubview(homeTopSlot)NSLayoutConstraint.activate([homeTopSlot.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),homeTopSlot.leadingAnchor.constraint(equalTo: view.leadingAnchor),homeTopSlot.trailingAnchor.constraint(equalTo: view.trailingAnchor),])homeTopSlot.screen = .home // setting the screen starts the slot}} -
Run the app. If the configuration has a campaign for
home_topand you are in its treatment arm, the component appears at the top of the home screen.
Check it in the debug view
Section titled “Check it in the debug view”Open the debug view from a debug-only menu or button in your app, inside #if DEBUG:
#if DEBUGimport GronaDebug#endif
// In a debug-only menu of a view controller#if DEBUGGrona.presentDebugView(from: self)#endifIn SwiftUI, present GronaDebugView() in a sheet instead. The view shows the configuration the SDK applied, the slots on screen, each campaign with your arm, and recent events. It opens only when GronaDebug was compiled with the DEBUG condition; otherwise the call does nothing. See Test and debug.
If the slot is empty, check your arm for the campaign. Control and holdout show nothing by design. To see the treatment on your device, pin the variant locally from the debug view or open a preview link.
Next steps
Section titled “Next steps”- Identify shoppers and pass their basket and context.
- Render Grona components in more placements.
- Host-rendered decisions to draw content with your own views.
- App Store privacy and privacy manifest before you release.