Miniwall Docs
Mobile SDK

Present a screen

Open a screen over your app and get a result when the user buys, restores, closes, acts or is skipped.

present opens a screen over your app. It finishes when the user is done with it. Use it for upgrade buttons, feature gates, prompts and any moment your own code decides.

@MainActor
@discardableResult
static func present(_ source: ScreenSource, options: PresentOptions = PresentOptions(), from presenter: UIViewController? = nil) async throws -> PurchaseResult

// Shorthand for a placement:
static func present(placement: String, options: PresentOptions = PresentOptions()) async throws -> PurchaseResult
Miniwall.configure(apiKey: "pk_...")

var options = PresentOptions()
options.products = [
    "monthly": ProductInfo(price: 9.99, currency: "USD", period: .every(.month)),
    "annual": ProductInfo(price: 59.99, currency: "USD", period: .every(.year),
                          offer: .init(price: 0, period: .every(.day, count: 7))),
]
options.onPurchase = { request in try await buy(request.packageId) }

let result = try await Miniwall.present(placement: "upgrade_button", options: options)

Where the screen comes from

SourceLoadsAnalytics
Placement (.placement, ScreenSource.Placement, { placement })The screen the placement shows now: its A/B test, schedule, audiences, frequency limit, holdout and display format apply. Recommended. Needs the API key.Yes
Paywall ID (.paywallId, ScreenSource.PaywallId, { paywallId })That screen's published version. Needs the API key.Yes
JSON (.json, ScreenSource.Json, { json })A screen JSON you bundle in the app, such as a file downloaded from the dashboard.None

A JSON source must be a screen: components_config.base has to exist. Otherwise present throws Miniwall: not a screen JSON (expected components_config.base.stack).

Options

Every option is optional.

How it looks

OptionTypeDefaultWhat it does
stylefullscreen, modal, sheet, banner (top or bottom) or toast (seconds)the placement's display, else full screenFormat. When you pass it, the placement's display is ignored. See Display modes.
colorModelight, dark, systemfrom configure, else systemColour scheme.
localestringfrom configure, else the device'sLanguage, such as vi_VN. If the screen has no such language, a language with the same first part is used, then the screen's default.
screensmap of screen ID to JSONnoneFor JSON sources only: the screens that a "Go to another screen" button can open, by screen ID. Screens loaded by placement or paywall ID fetch linked screens from Miniwall instead.

Prices and text

OptionTypeWhat it does
productsmap of package ID to ProductInfoA price for each package ID. Fills every {{ product.* }} text. See Purchases and prices.
variablesPerPackagemap of package ID to a map of text valuesExtra {{ product.* }} values per package. They override the computed ones.
customVariablesmap of strings, numbers or booleansValues for {{ custom.name }} in the screen's text. Shown as plain text.

Callbacks

OptionCalled whenReturn value
onPurchaseThe user taps a purchase button.true: purchased. false: cancelled, the screen stays open. Throw: failure, the screen stays open and onError is called.
onRestoreThe user taps Restore purchases. The button only appears when you pass this.true: restored. false: nothing restored, the screen stays open.
onActionA "Run your code" button is pressed.false keeps the screen open. Anything else closes it.
onAnswerThe user changes an answer.none
onOpenUrlA link in the screen is pressed. Default: open in the system browser.none
onErrorSomething failed after the screen opened.none

Full details are on Callbacks.

Targeting

OptionTypeWhat it does
attributesmapWhat you know about the user, merged over the ones from configure and setAttributes for this call. Used by the placement's audiences. Never sent to Miniwall. See Targeting attributes.

The result

ResultiOSAndroidReact Native and Flutter (outcome)
Purchased.purchased(packageId, answers)PurchaseResult.Purchasedpurchased
Restored.restored(answers)PurchaseResult.Restoredrestored
Closed.closed(answers)PurchaseResult.Closedclosed
Action.action(actionId, answers)PurchaseResult.ActionTakenaction
Skipped.skipped(reason)PurchaseResult.Skippedskipped

answers holds what the user answered. See Answers. skipped means nothing opened; its reason is frequency_cap, audience or holdout.

present throws (or the promise rejects) when the screen cannot be loaded, for example when the key is wrong or the network failed with no saved copy. Wrap the call in try/catch. See Errors.

do {
    switch try await Miniwall.present(placement: "upgrade_button", options: options) {
    case let .purchased(packageId, _): unlockPro(packageId)
    case let .action(actionId, answers): handleAction(actionId, answers)
    case .skipped: break // the placement chose not to show
    default: break       // closed or restored
    }
} catch {
    print("Could not show the screen:", error)
}

What happens while it is open

  • While your onPurchase, onRestore or onAction runs, the screen shows a busy spinner and ignores further taps, so a double tap cannot start two purchases.
  • A screen with "Go to another screen" buttons moves between screens inside the same presentation. present finishes once, when the flow ends. The first screen is the one the placement returns, so its A/B test, schedule and audiences apply to the whole flow. See Answers.
  • A sheet (popup, slide-in) closes with the system gesture: swipe down on iOS, tap outside or system back on Android. A full-screen cover has no gesture on iOS, and closes with system back on Android. On every platform the screen can also carry a button that uses the action Close the screen; the SDK adds no close button of its own. Closing finishes with closed.
  • On Android, the system back button closes an open sheet inside the screen first, then the screen.
  • Links open in the system browser unless you handle onOpenUrl. Only http, https, mailto, tel and links that start with a single / are ever opened.

With a placement

When the source is a placement the SDK also applies the placement's setup in the dashboard: which screen, its A/B test, a schedule in force, audiences, the frequency limit, the holdout and the display format. See Placements.

Views, closes, purchase taps, purchases, restores, button presses and chosen answers are reported to your Analytics unless you set analytics to false. See Events and privacy.

On this page