We decided it was time to build and ship a brand new app using SwiftUI to experience first hand the latest from Apple.
The app, now available on the App Store, is called Serenity Now.
It’s a simple audio player with a SwiftUI interface and AVFoundation under the hood. The app is filled with a collection of sounds for focus, relaxation, and better sleep.
We’re in the monetization business, so we set out to implement our own Nami SDK to offer IAPs and subscriptions.
Here are the three products we want to offer:
Users who buy one of these products gets access to our Serenity Now Premium features. Those features include:
We want users to encounter our paywall during the app experience in three different places:
Now that our goals are clear, let’s see what it takes to accomplish.
SwiftUI apps don’t use the the AppDelegate we have all come to know from years of building iOS apps. Instead, the entry point for SwiftUI apps is a struct conforming to the App protocol.
That protocol has one requirement, which is the implementation of a property called body. Digging into the specifics of body is outside the scope of this article, but suffice it to say that is where you’ll kick off your app’s user interface.
For our purposes, we need a place to configure the Nami SDK. It turns out, we can provide a custom init() method on our App struct for this purpose.
Here we setup our NamiConfiguration object with our app’s unique appPlatformID (found in the Nami Control Center > Integrations > [your Apple App Store integration]
We will need to monitor changes to the the entitlements a user has access to. A change may occur if they buy one of our IAP products, or if they have a subscription that expires.
In Serenity Now, if a user buys any one of the three IAP products, we grant them access to an entitlement called premium_access.
In SwiftUI, we can monitor for user entitlement changes by setting up an ObservableObject. Specifically, we need to register with the Nami SDK’s registerEntitlementsChangedHandler callback to update our ObservableObject’s premium var.
Since premium has a @Published property wrapper, any SwiftUI view’s body will be re-invoked any time the value changes.
This is exactly what we want. If a user buys one of our products, we want any of the SwiftUI views that rely on the value of premium, to be updated. Concretely, this means any view that gates access based upon the premium_access entitlement will update if the value changes.
Next, we want to show our paywall the first time the user launches the app.
In our main SwiftUI view, SerenityNowHome, we run some code in .onAppear that checks UserDefaults for the value of a key didLaunchBefore. If the value is false, it’s the first launch of our app so we tell the Nami SDK to show a paywall if NamiDataSource’s premium var is False.
Next, we want to gate access to certain premium content in our app.
If the user does not have access to the premium content (e.g. NamiDataSource’s premium var is False), we want to present the paywall. Otherwise, we want to show the SwiftUI with the relevant content.
Finally, we want to give users a way to access our paywall from a marketing tile in the Settings section of our app.
In our Settings view, if the user does not have premium access (via our NamiDataSource), we show our NamiUpsellBannerView. If the user taps on it, we show our paywall.
If the user does have premium access, we instead show a link to Manage subscription which takes the user to the system Settings.
There is much more we want to do with our SwiftUI app and some advanced use cases we want to discuss in future blog posts. For now, we hope this article shows how is it is to get up and running with some very common use cases for selling IAPs and subscriptions.
You may be wondering about the paywall view itself. The paywall was created via Nami’s no-code paywall designer. The Nami SDK provides a native Swift that is configurable from the Nami Control Center so you can make changes instantly.
The SDK provided paywall integrates seamlessly with our SwiftUI app from both a user interface and usability perspective. If you’re interested in giving Nami a spin for your own SwiftUI app, you can create a free account here.
Step-by-step guide to configuring Apple Offer Codes in App Store Connect in 2023. Learn to quickly setup winback and new subscriber offers in iTunes.
In order to address StoreKit's unhandled error "Payment Sheet Failed" when performing simulator in-app purchase testing, follow this solution.