My app displays a Paywall right away. Everything should be working fine as of my tests, but every time I upload the app update to App Store connect, they tell me that there is a problem: Users are not able to get access to premium features after they purchase the in-app subscription (i.e. the Paywall is not gone away after a successful purchase).
My approach to grant access to users, is as following:
1- Check user access in the main app view:
If the user has no access, a Paywall modal sheet should be shown to prevent users from using the app, and provide subscription options. Otherwise, if the user already has purchased subscription, he will be granted access and app won't show him the Paywall modal sheet.
Assuming that I'm using RevenueCat SDK instead of StoreKit to process in-app purchases, the checking process will be inside .onAppear
of ContentView
:
@main
struct MyApp: App {
var body: some Scene {
DocumentView()
}
}
struct DocumentView: Scene {
@State var showModal = false
var body: some Scene {
DocumentGroup(newDocument: Document()) { file in
ContentView(document: file.$document, showModal: $showModal)
.onAppear() {
Purchases.shared.getCustomerInfo { customerInfo, error in
// Check the info parameter for active entitlements
if customerInfo?.entitlements[Constant.entitlementId]?.isActive == true {
// Grant access to user
self.showModal = false
UserDefaults.standard.setValue(true, forKey: Constant.userDefaultsAccess)
} else {
// Revoke access from user
self.showModal = true
UserDefaults.standard.setValue(false, forKey: Constant.userDefaultsAccess)
}
}
}
}
}
}
2- Show the content view with/without Paywall modal:
Now, in ContentView
, I am displaying the modal sheet only if showModal
is true
(i.e. if user has NOT been granted access).
In addition, I am using another variable: selection
, because there might be other modal sheets to display (like About, Info, etc.).
Furthermore, the selection
could be Pro
to show the Paywall modal sheet along with X image button in the corner, or NoPro
to show it without X image button.
struct ContentView: View {
@State private var selection: String? = "NoPro"
@Binding var showModal: Bool
var body: some View {
NavigationView {
VStack {
....
}
}
.navigationBarItems(
trailing:
HStack {
Button(action: {
self.selection = "Pro"
self.showModal.toggle()
}, label: {
Image(systemName: UserDefaults.standard.bool(forKey: Constant.userDefaultsAccess) ? "checkmark.seal.fill" : "xmark.seal.fill")
})
.foregroundColor(UserDefaults.standard.bool(forKey: Constant.userDefaultsAccess) ? .green : .red)
.sheet(isPresented: $showModal) {
if self.selection == "Pro" {
Paywall(showModal: self.$showModal, pro: "Pro")
} else if self.selection == "NoPro" {
Paywall(showModal: self.$showModal, pro: "NoPro")
}
}
}
}
3- In the Paywall, when user purchases, hide the modal sheet:
This is the third layer of the app, the Paywall modal sheet.
Here, the user will be able to purchase the subscription, and if he do so, the Paywall should disappear automatically.
In addition, if the user has opened the Paywall modal sheet while he is already subscribed (for example, to see subscription info), he will be able to close the Paywall by tapping on the X image button, or by swiping down.
struct Paywall: View {
@Binding var showModal: Bool
@State var pro: String
var body: some View {
VStack {
....
// If user has access, show a closing X image button
if pro == "Pro" {
HStack {
Spacer()
VStack {
Image(systemName: "xmark.circle.fill")
.font(.largeTitle)
.padding()
.onTapGesture(count: 1, perform: {
self.showModal.toggle()
})
Spacer()
}
}
}
SubscriptionButton(showModal: $showModal)
}
.interactiveDismissDisabled(pro == "Pro" ? false : true)
}
}
struct SubscriptionButton: View {
@Binding var showModal: Bool
var body: some View {
if UserDefaults.standard.bool(forKey: Constant.userDefaultsAccess) {
Text("You are already Subscribed!")
} else {
Button(action: {
PurchaseManager.purchase(productId: Constant.productId) {
UserDefaults.standard.setValue(true, forKey: Constant.userDefaultsAccess)
showModal = false
}
}, label: {
VStack {
Text("Subscribe now!")
}
})
}
}
}
Is there anything wrong with this approach? What's the best way to do it?