Is it possible to to lock widget functionality behind a in-app purchase paywall

Hey,

Is it possible to only offer a certain amount of widget functionality to users who have purchased an in-app purchase or are ongoing subscribers?

Thanks!

Replies

Yes it's possible to paywall a widget. What I did was:

  • Design two Views, one which is a paywall view, the other is the actual Widget. (You could in theory also design more views, so that users on higher paying tiers get more widget functionality.)

Highly simplified example:

// Actual widget view 
struct WidgetView: View {
    var entry: WidgetEntry
    var body: some View {

        VStack(alignment: .leading) {
            Spacer()  
            Text(entry.widgetText)
                .font(.system(size: 12, weight: .semibold, design: .default))
                .padding(.leading, 16)
                .padding(.trailing, 16)
                .padding(.bottom, 16)
                .frame(maxWidth: .infinity, alignment: .leading)
                .foregroundColor(.white)
        }
    }
}

// Paywall widget view
struct WidgetPaywallView: View {
    var body: some View {
        VStack(alignment: .leading) {
            Spacer()
            Text("Subscribe to use the widget")
                .font(.system(size: 12, weight: .semibold, design: .default))
                .padding(.leading, 16)
                .padding(.trailing, 16)
                .padding(.bottom, 16)
                .frame(maxWidth: .infinity, alignment: .leading)
                .foregroundColor(.white)
        }
    }
}
  • In your widget timeline entry model, add a Boolean var such as "isPremiumSubscriber"
struct WidgetEntry: TimelineEntry {
    let date: Date
    let widgetText: String
    var isPremiumSubscriber: Bool
}
  • In getTimeline() in your Timeline Provider, check to see if the user is subscribed (or, in my case, I use RevenueCat to manage the in-app subscriptions as it's much easier that way). If they are subscribed, create your timeline entries with the timestamp when the widget will be updated (e.g., every 20 mins), the data you want to display at that time (in the code example I pasted, it's just a simple String), and then set the value of isPremiumSubscriber = true.
  • Then, in your Widget view (which is called each time your widget is about to be refreshed), get the data from your entry, and if entry.isPremiumSubscriber == true, return the actual widget view with all the functionality; if it's false, then return the paywall widget view.

Note that with this approach, if the user's subscription status lapses midway through an already created timeline, then the user will still have widget functionality until the timeline runs out and getTimeline() is called again.

struct WidgetEntryView: View {
    var entry: WidgetEntry
    var body: some View {

         // Display widget if user is subscribed, otherwise return the paywall view
        if entry.isPremiumSubscriber {
            WidgetView(entry: entry)
        } else {
            WidgetPaywallView()
        }
    }
}
  • Finally, in your actual app, you also need to add a way to force refresh the widget if the user has just subscribed. You can use WidgetCenter.shared.reloadAllTimelines() for this. In my case, I have a global boolean in AppDelegate.swift which keeps track if the user is subscribed. I added a didSet that fires whenever the value changes-- and I add a check to see if the new value is different from the old value. If it's different, I call WidgetCenter.shared.reloadAllTimelines(), which will call getTimeline() in your widget. Make sure to import WidgetKit too before you use reloadAllTimelines().
var isPremiumSubscriber: Bool = false {
        didSet {
            if #available(iOS 14.0, *) {
                if isPremiumSubscriber != oldValue {

                    // Update widgets because subscription status has changed
                    WidgetCenter.shared.reloadAllTimelines()
                }
            }
        }
 }
  • Great explanation but I don't get how do you check with RevenueCat if the premium is active in the getTimeline() method. Can you explain that? Thank you!

Add a Comment

Would also love more of an explanation on the logic in getTimeline(). I continue to get an error when trying to work with the SDK from within the extension that no such module exists.

  • You can check and write current premium status to appgroup's user defaults from the app, and then check it in the widget (from the same appgroup)

Add a Comment