RevenueCat Paywall
Beautiful paywall UI for subscription conversion

Preview

Code
1import SwiftUI23struct PaywallView: View {4 @State private var selectedPlan: SubscriptionPlan = .monthly5 @State private var isLoading = false6 7 var body: some View {8 VStack(spacing: 0) {9 // Header10 VStack(spacing: 16) {11 Image(systemName: "crown.fill")12 .font(.system(size: 60))13 .foregroundColor(.yellow)14 15 Text("Unlock Premium")16 .font(.largeTitle)17 .fontWeight(.bold)18 19 Text("Get unlimited access to all features")20 .font(.body)21 .foregroundColor(.secondary)22 .multilineTextAlignment(.center)23 }24 .padding(.top, 40)25 .padding(.horizontal, 24)26 27 // Features28 VStack(spacing: 16) {29 FeatureItem(icon: "infinity", title: "Unlimited Projects", description: "Create as many projects as you need")30 FeatureItem(icon: "chart.line.uptrend.xyaxis", title: "Advanced Analytics", description: "Detailed insights and reporting")31 FeatureItem(icon: "headphones", title: "Priority Support", description: "24/7 dedicated customer support")32 FeatureItem(icon: "sparkles", title: "Premium Templates", description: "Access to exclusive design templates")33 }34 .padding(.horizontal, 24)35 .padding(.top, 32)36 37 Spacer()38 39 // Pricing Plans40 VStack(spacing: 12) {41 HStack {42 PlanButton(plan: .monthly, isSelected: selectedPlan == .monthly) {43 selectedPlan = .monthly44 }45 PlanButton(plan: .yearly, isSelected: selectedPlan == .yearly) {46 selectedPlan = .yearly47 }48 }49 50 Button(action: {51 purchasePlan()52 }) {53 HStack {54 if isLoading {55 ProgressView()56 .scaleEffect(0.8)57 }58 Text(isLoading ? "Processing..." : "Start Free Trial")59 .fontWeight(.semibold)60 }61 .frame(maxWidth: .infinity)62 .padding()63 .background(Color.blue)64 .foregroundColor(.white)65 .cornerRadius(12)66 }67 .disabled(isLoading)68 69 Text("Cancel anytime • 7-day free trial")70 .font(.caption)71 .foregroundColor(.secondary)72 }73 .padding(.horizontal, 24)74 .padding(.bottom, 40)75 }76 .background(Color(.systemBackground))77 }78 79 func purchasePlan() {80 isLoading = true81 // RevenueCat purchase logic82 DispatchQueue.main.asyncAfter(deadline: .now() + 2) {83 isLoading = false84 }85 }86}8788struct FeatureItem: View {89 let icon: String90 let title: String91 let description: String92 93 var body: some View {94 HStack(spacing: 16) {95 Image(systemName: icon)96 .font(.title2)97 .foregroundColor(.blue)98 .frame(width: 24)99 100 VStack(alignment: .leading, spacing: 4) {101 Text(title)102 .font(.headline)103 Text(description)104 .font(.caption)105 .foregroundColor(.secondary)106 }107 108 Spacer()109 }110 }111}112113struct PlanButton: View {114 let plan: SubscriptionPlan115 let isSelected: Bool116 let action: () -> Void117 118 var body: some View {119 Button(action: action) {120 VStack(spacing: 4) {121 Text(plan.title)122 .font(.headline)123 Text(plan.price)124 .font(.title2)125 .fontWeight(.bold)126 .foregroundColor(isSelected ? .blue : .primary)127 }128 .frame(maxWidth: .infinity)129 .padding()130 .background(isSelected ? Color.blue.opacity(0.1) : Color(.systemGray6))131 .cornerRadius(8)132 .overlay(133 RoundedRectangle(cornerRadius: 8)134 .stroke(isSelected ? Color.blue : Color.clear, lineWidth: 2)135 )136 }137 .buttonStyle(.plain)138 }139}140141enum SubscriptionPlan {142 case monthly, yearly143 144 var title: String {145 switch self {146 case .monthly: return "Monthly"147 case .yearly: return "Yearly"148 }149 }150 151 var price: String {152 switch self {153 case .monthly: return "$9.99/mo"154 case .yearly: return "$99.99/yr"155 }156 }157}158159struct PaywallView_Previews: PreviewProvider {160 static var previews: some View {161 PaywallView()162 }163}