Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added content/docs/images/article-vid-example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added content/docs/images/showCustomAction.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
148 changes: 148 additions & 0 deletions content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx
Original file line number Diff line number Diff line change
@@ -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:

<div className="flex justify-center">
<Frame>![](/images/article-vid-example.gif)</Frame>
</div>

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.

<Warning>
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.
</Warning>

## 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:

<Frame>![](/images/showCustomAction.jpeg)</Frame>

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.
1 change: 1 addition & 0 deletions content/docs/ios/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down