diff --git a/content/docs/images/article-vid-example.gif b/content/docs/images/article-vid-example.gif new file mode 100644 index 0000000..e5165d4 Binary files /dev/null and b/content/docs/images/article-vid-example.gif differ diff --git a/content/docs/images/showCustomAction.jpeg b/content/docs/images/showCustomAction.jpeg new file mode 100644 index 0000000..66e9cee Binary files /dev/null and b/content/docs/images/showCustomAction.jpeg differ diff --git a/content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx b/content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx new file mode 100644 index 0000000..5a06fa2 --- /dev/null +++ b/content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx @@ -0,0 +1,148 @@ +--- +title: "Article-Style Paywalls: Inline with Additional Plans" +description: "Embed an inline paywall in a scrollable article and optionally present a second full-screen paywall for additional plans." +--- + +Article-style paywalls let you keep readers in the flow of a long-form page while still prompting for upgrade options. You can place an inline paywall inside a scroll view, then present a second, full-screen paywall when users tap “see more plans.” + +This pattern is common in paid media and magazine apps: a portion of the article is readable, the rest is blurred or gated, and a footer paywall offers an inline purchase with a “see more plans” option that opens a full-screen paywall. Check out this working example: + +
+ ![](/images/article-vid-example.gif) +
+ +This guide will show you how to build this example by explaining the APIs involved, and then a full code sample. There’s also a live working example in [CaffeinePal](https://github.com/superwall/CaffeinePal/tree/using-superwall-sdk). Look at the `RecipesView` to see it in action. + +## Key APIs + +Use `getPaywall()` to fetch a paywall you can embed inline, and configure it with a placement so you can control which paywall variant shows from the dashboard. For more on presenting paywalls in custom presentations, check out our [blog post](https://superwall.com/blog/custom-paywall-presentation-in-ios-with-the-superwall-sdk/). + +For the second paywall, trigger a custom action from the inline paywall and call `getPaywall()` again to present the full-screen option. + + +You're responsible for removing embedded paywall views when users move on. Reusing the same `PaywallViewController` or `PaywallView` instance elsewhere can cause a crash. For UIKit, avoid mixing `register()` and `getPaywall()` when you embed paywalls. + + +## Presenting a second paywall + +To get the inline paywall to trigger a second, full-screen paywall, create a custom action in the paywall editor in the embedded paywall. In this example, a custom action called "showFromLine" is triggered from the "or, view all plans" button: + +![](/images/showCustomAction.jpeg) + +Then, respond to that action in your [`SuperwallDelegate`](/ios/sdk-reference/SuperwallDelegate) to retrieve the second paywall and present it. In the code below, our second paywall is normally triggered via the `showAllPlansPaywall` placement that was setup in the Superwall dashboard within a campaign: + +```swift +extension MyAppLogic: SuperwallDelegate, PaywallViewControllerDelegate { + // Custom action comes in + func handleCustomPaywallAction(withName name: String) { + if name == "showFromInline" { + Task { + await presentAllPlansPaywall() + } + } + } + + // MARK: PaywallViewControllerDelegate + + func paywall( + _ paywall: PaywallViewController, + didFinishWith result: PaywallResult, + shouldDismiss: Bool + ) { + if shouldDismiss { + paywall.dismiss(animated: true) + } + } + + func paywall( + _ paywall: PaywallViewController, + loadingStateDidChange loadingState: PaywallLoadingState + ) { + // Handle loading state changes if needed + } + + // MARK: Custom Paywall Presentation + + private func presentAllPlansPaywall() async { + do { + let paywallViewController = try await Superwall.shared.getPaywall( + forPlacement: "showAllPlansPaywall", + delegate: self + ) + + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let rootViewController = windowScene.windows.first?.rootViewController else { + return + } + + var topController = rootViewController + while let presented = topController.presentedViewController { + topController = presented + } + + topController.present(paywallViewController, animated: true) + } catch let reason as PaywallSkippedReason { + print("Paywall skipped: \(reason)") + } catch { + print("Error presenting paywall: \(error)") + } + } +} +``` + +This keeps the inline paywall embedded while you intentionally present the next paywall. The entire flow looks like this: + +**In your dashboard** +1. Have a paywall setup for your "footer" or bottom paywall. +2. Add a custom action to it to present a second paywall over it. +3. Make sure both paywalls are active in a campaign, and remember the placements used to trigger them + +**In your code** +1. Use `getPaywall` and `PaywallView` to embed the first paywall in your scrollview. +2. Users can purchase from there, or tap another button to present a second paywall. +3. Handle a custom action fired from a "View all plans" or similar button in a `SuperwallDelegate`. +4. Use `PaywallViewControllerDelegate` to manage presentation of the second one. + +Here's some code to model your approach, showing the first paywall as either an overlay at the bottom or inline with scrolled content: + +```swift +enum PaywallEmbedMode { + case overlay + case inline +} + +struct ArticlePaywallDemoView: View { + let mode: PaywallEmbedMode + let placement: String = "getPaywallTest" + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + Text("How to embed a Superwall paywall alongside your own content") + .font(.title) + Text("By Superwall").font(.caption) + Text("...article content...") + .padding(.vertical, 16) + + if mode == .inline { + paywallContent + } + } + .padding() + .overlay(alignment: .bottom) { + if mode == .overlay { + paywallContent + } + } + } + } + + private var paywallContent: some View { + PaywallView(placement: placement) + .frame(maxWidth: .infinity) + .frame(height: 300) + } +} +``` + +If you need to remove the paywall, remove the `PaywallView` from the view hierarchy and recreate it when you need to show it again. diff --git a/content/docs/ios/meta.json b/content/docs/ios/meta.json index 3ad33ed..73b0a1b 100644 --- a/content/docs/ios/meta.json +++ b/content/docs/ios/meta.json @@ -57,6 +57,7 @@ "guides/using-revenuecat", "guides/experimental-flags", "guides/testing-purchases", + "guides/embedded-paywalls-in-scrollviews", "guides/superwall-deep-links", "guides/app-privacy-nutrition-labels", "guides/advanced",