Display a paywall
Present the paywall or flow assigned to a placement and react to how it ended.
The initial release is observer-only
SDK-started purchases are temporarily unavailable. Paywalls and flows show, but a purchase started from one is refused and the paywall shows it as failed. Keep your own purchase path, as described in Build a custom purchase screen.
Use this page to show a paywall or a multi-screen flow, such as an onboarding survey, from the
Swift SDK. A paywall is a flow with one screen. A placement is the stable slug your app
asks for, such as onboarding or settings-upsell. You publish paywalls and assign them to
placements in Studio, as described in Paywall locations.
Add the renderer
Paywalls are drawn natively by the VoidhashUI package. Add its VoidhashUI product to your app
target next to Voidhash, and pass the renderer when you configure the SDK.
import Voidhash
import VoidhashUI
var options = VoidhashOptions()
options.paywallRenderer = VoidhashUIPaywallRenderer()
let voidhash = Voidhash.configure(publishableKey: "vh_pk_…", options: options)Without the renderer, presenting fails with FlowError.rendererMissing and the SDK makes no
paywall requests.
Choose what flows may do
Purchases, restores, closing and analytics always work. Anything else a flow can do is off until
you grant it with flowCapabilities. By default a flow may only open links.
options.flowCapabilities = [.links, .notifications, .attributes]With .notifications, a flow can ask the user to allow notifications. With .tracking, it can
ask for App Tracking Transparency permission, which also needs NSUserTrackingUsageDescription
in your Info.plist. With .attributes, it can save answers as person attributes, the same way
setPersonAttributes does. An action you did not grant is skipped and reported through
onWarning.
Present it
Ask for a placement, and the SDK presents whichever paywall or flow is assigned to it. The call returns when the flow ends.
switch await voidhash.presentFlow(placement: "settings-upsell") {
case .purchased(let productId):
await refreshAccess()
case .restored:
await refreshAccess()
case .finished(let result, let variables):
handleOnboarding(result, variables)
case .closed:
break
case .failed(let error):
showOwnUpgradeScreen()
}.purchased and .restored mean a purchase or restore started from the paywall completed, and
the paywall was dismissed. .finished means the flow reached one of its finish actions: result
is the name you gave that action in Studio and variables holds the flow's variables, such as the
answers to a survey. .closed means the user closed it, or you called dismissFlow(). A failed
or cancelled purchase does not end the flow, so the user can try again.
The paywall is presented full screen from the key window. UIKit apps can pass the view controller to present from.
let result = await voidhash.presentFlow(placement: "onboarding", from: viewController)You can also close it from your own code, and the pending presentFlow call returns .closed.
await voidhash.dismissFlow()Resume where the user left
A flow saves its progress on the device as the user moves through it, for each person and flow.
When the same flow is presented again, it resumes with those answers instead of starting over.
Finishing the flow or completing a purchase from it clears the saved progress, and so does
switching to another person with identify or reset.
Fall back when nothing was shown
.failed carries a FlowError that says why nothing was shown:
.notAssigned: no published paywall is assigned to the placement. This is the normal answer for a placement you have not set up yet..unavailable: the device has never loaded this placement and cannot reach Voidhash..rendererMissing: the app did not passVoidhashUIPaywallRenderer()..unsupportedPackage: the paywall was published for a newer SDK. Update the SDK..presentationFailed: there was no window to present from, or the paywall could not be drawn..disabled: the SDK is disabled or shut down.
Keep a fallback for important entry points so the customer still has a way to upgrade. Once a placement has been shown, the device keeps its paywall, so it shows again offline.
Analytics
Events a flow tracks are captured with paywall_location and paywall_id properties, and each
screen it shows is recorded as a $screen event, so flows appear in your funnels next to your own
screens.