Files
purchases-ios/Examples/MagicWeatherSwiftUI/Shared/Sources/Views/PaywallView.swift
T

224 lines
7.1 KiB
Swift
Raw Normal View History

2021-01-29 14:36:54 -05:00
//
// PaywallView.swift
// Magic Weather SwiftUI
//
// Created by Cody Kerns on 1/19/21.
//
import SwiftUI
2021-09-03 14:17:37 -03:00
import RevenueCat
2021-01-29 14:36:54 -05:00
/*
An example paywall that uses the current offering.
*/
struct PaywallView: View {
/// - This binding is passed from ContentView: `paywallPresented`
@Binding var isPresented: Bool
/// - This can change during the lifetime of the PaywallView (e.g. if poor network conditions mean that loading offerings is slow)
/// So set this as an observed object to trigger view updates as necessary
@ObservedObject var userViewModel = UserViewModel.shared
2021-01-29 14:36:54 -05:00
/// - The current offering saved from PurchasesDelegateHandler
/// if this is nil, then you might want to show a loading indicator or similar
private var offering: Offering? {
userViewModel.offerings?.current
}
var body: some View {
PaywallContent(offering: self.offering, isPresented: self.$isPresented)
}
}
private struct PaywallContent: View {
var offering: Offering?
var isPresented: Binding<Bool>
/// - State for displaying an overlay view
@State private var isPurchasing: Bool = false
@State private var error: NSError?
@State private var displayError: Bool = false
2021-01-29 14:36:54 -05:00
var body: some View {
NavigationView {
ZStack {
/// - The paywall view list displaying each package
List {
Section(header: Text("\nMagic Weather Premium"), footer: Text(Self.footerText)) {
2021-01-29 14:36:54 -05:00
ForEach(offering?.availablePackages ?? []) { package in
PackageCellView(package: package) { (package) in
2021-01-29 14:36:54 -05:00
/// - Set 'isPurchasing' state to `true`
isPurchasing = true
2021-01-29 14:36:54 -05:00
/// - Purchase a package
do {
let result = try await Purchases.shared.purchase(package: package)
2021-01-29 14:36:54 -05:00
/// - Set 'isPurchasing' state to `false`
self.isPurchasing = false
if !result.userCancelled {
self.isPresented.wrappedValue = false
2021-01-29 14:36:54 -05:00
}
} catch {
self.isPurchasing = false
self.error = error as NSError
self.displayError = true
2021-01-29 14:36:54 -05:00
}
}
}
}
}
.listStyle(InsetGroupedListStyle())
2021-01-29 14:36:54 -05:00
.navigationBarTitle("✨ Magic Weather Premium")
.navigationBarTitleDisplayMode(.inline)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.bottom)
2021-01-29 14:36:54 -05:00
/// - Display an overlay during a purchase
Rectangle()
.foregroundColor(Color.black)
.opacity(isPurchasing ? 0.5: 0.0)
.edgesIgnoringSafeArea(.all)
}
}
.navigationViewStyle(StackNavigationViewStyle())
.colorScheme(.dark)
.alert(
isPresented: self.$displayError,
error: self.error,
actions: { _ in
Button(role: .cancel,
action: { self.displayError = false },
label: { Text("OK") })
},
message: { Text($0.recoverySuggestion ?? "Please try again") }
)
2021-01-29 14:36:54 -05:00
}
private static let footerText = "Don't forget to add your subscription terms and conditions. Read more about this here: https://www.revenuecat.com/blog/schedule-2-section-3-8-b"
2021-01-29 14:36:54 -05:00
}
/* The cell view for each package */
private struct PackageCellView: View {
2021-10-22 15:59:13 -07:00
let package: Package
let onSelection: (Package) async -> Void
2021-01-29 14:36:54 -05:00
var body: some View {
Button {
Task {
await self.onSelection(self.package)
}
} label: {
self.buttonLabel
}
.buttonStyle(.plain)
}
private var buttonLabel: some View {
2021-01-29 14:36:54 -05:00
HStack {
VStack {
HStack {
Text(package.storeProduct.localizedTitle)
2021-01-29 14:36:54 -05:00
.font(.title3)
.bold()
Spacer()
}
HStack {
Text(package.terms)
2021-01-29 14:36:54 -05:00
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
}
}
.padding([.top, .bottom], 8.0)
Spacer()
Text(package.localizedPriceString)
.font(.title3)
.bold()
}
.contentShape(Rectangle()) // Make the whole cell tappable
2021-01-29 14:36:54 -05:00
}
2021-01-29 14:36:54 -05:00
}
extension NSError: LocalizedError {
public var errorDescription: String? {
return self.localizedDescription
}
}
struct PaywallView_Previews: PreviewProvider {
private static let product1 = TestStoreProduct(
localizedTitle: "PRO monthly",
price: 3.99,
localizedPriceString: "$3.99",
productIdentifier: "com.revenuecat.product",
productType: .autoRenewableSubscription,
localizedDescription: "Description",
subscriptionGroupIdentifier: "group",
subscriptionPeriod: .init(value: 1, unit: .month),
introductoryDiscount: .init(
identifier: "intro",
price: 0,
localizedPriceString: "$0.00",
paymentMode: .freeTrial,
subscriptionPeriod: .init(value: 1, unit: .week),
numberOfPeriods: 1,
type: .introductory
),
discounts: []
)
private static let product2 = TestStoreProduct(
localizedTitle: "PRO annual",
price: 34.99,
localizedPriceString: "$34.99",
productIdentifier: "com.revenuecat.product",
productType: .autoRenewableSubscription,
localizedDescription: "Description",
subscriptionGroupIdentifier: "group",
subscriptionPeriod: .init(value: 1, unit: .year),
introductoryDiscount: nil,
discounts: []
)
private static let offering = Offering(
identifier: Self.offeringIdentifier,
serverDescription: "Main offering",
metadata: [:],
availablePackages: [
.init(
identifier: "monthly",
packageType: .monthly,
storeProduct: product1.toStoreProduct(),
offeringIdentifier: Self.offeringIdentifier
),
.init(
identifier: "annual",
packageType: .annual,
storeProduct: product2.toStoreProduct(),
offeringIdentifier: Self.offeringIdentifier
)
]
)
private static let offeringIdentifier = "offering"
static var previews: some View {
PaywallContent(offering: Self.offering, isPresented: .constant(true))
}
}