
你好 Apphud的Connected Denis-一种服务,用于分析iOS应用程序的可再生订阅。
如您所知,Apple在WWDC 2019上宣布了其新的声明性SwiftUI框架。 在本文中,我将尝试说明如何使用SwiftUI进行付款屏幕和实现自动续订的功能。
如果您还不熟悉SwiftUI,则可以阅读简短的介绍性文章 。 并且,如果您想了解有关订阅以及如何正确实现订阅的更多信息,请阅读本文 。
您需要Xcode 11才能工作 。 创建一个新项目,并确保“ Use SwiftUI”旁边有一个复选标记。
SwiftUI是用于编写接口的框架,因此我们无法使用该接口创建采购经理。 但是,我们不会写经理,而是使用现成的解决方案,并在其中补充代码。 您可以使用例如SwiftyStoreKit 。 在我们的示例中,我们将使用上一篇文章中的类 。
产品将在主屏幕上初始化,我们订阅的到期日期和切换到购买屏幕的按钮也将在那里显示。
ProductsStore.shared.initializeProducts() if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: ContentView(productsStore: ProductsStore.shared)) self.window = window window.makeKeyAndVisible() }
考虑SceneDelegate
类。 在其中,我们创建一个单例类ProductsStore
,在其中初始化产品。 之后,创建我们的根ContentView
并指定singleton作为输入参数。
考虑ProductsStore
类:
class ProductsStore : ObservableObject { static let shared = ProductsStore() @Published var products: [SKProduct] = [] @Published var anyString = "123"
这个小类,是IAPManager
上的“附加组件”,用于在更新产品列表时更新ContentView
。 ProductsStore
类支持ObservableObject
协议。
什么是ObservableObject
和@Published
?
ObservableObject
是用于观察对象并跟踪其属性变化的特殊协议。 必须使用@Published
属性标记属性。 在该示例中,当products
数组更改时会发送通知,但是您可以为对象的任何方法和属性添加此通知。
产品加载本身可以通过任何方式完成,但是在此请求结束时,您必须将产品数组分配给products变量。 如何聆听变化? 这是使用@ObservedObject
键参数完成的:
@ObservedObject var productsStore : ProductsStore
简而言之,这类似于通知中心。 为了使View
接受这些通知,您必须具有@ObservedObject
属性的此对象的变量。
让我们回到ProductsStore
类的逻辑。 其主要目的是下载和存储产品列表。 但是产品数组已经存储在IAPManager
,发生重复。 这不好,但是,首先,在本文中,我想向您展示如何实现对象合并,其次,并非总是可以更改采购经理的完成的类。 例如,如果您使用第三方库,则将无法添加ObservableObject
协议并发送通知。
值得注意的是,除了@ObservedObject
属性之外@ObservedObject
还有@State
属性可以帮助跟踪简单变量(例如String
或Int
)中的更改以及更具全局性的@EnvironmentObject
,后者可以立即更新应用程序中的所有View
而不必在对象之间传递变量。
让我们ContentView
开始屏幕:
struct ContentView : View { @ObservedObject var productsStore : ProductsStore @State var show_modal = false var body: some View { VStack() { ForEach (productsStore.products, id: \.self) { prod in Text(prod.subscriptionStatus()).lineLimit(nil).frame(height: 80) } Button(action: { print("Button Pushed") self.show_modal = true }) { Text("Present") }.sheet(isPresented: self.$show_modal) { PurchaseView() } } } }
让我们找出代码。 使用ForEach
我们创建文本View
,其数量等于产品的数量。 由于我们绑定了变量productsStore
,因此只要ProductsStore
类中的ProductsStore
数组发生更改,就将更新View
。
subscriptionStatus
方法是SKProduct
类SKProduct
一部分,并根据预订的到期日期返回所需的文本:
func subscriptionStatus() -> String { if let expDate = IAPManager.shared.expirationDateFor(productIdentifier) { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .medium let dateString = formatter.string(from: expDate) if Date() > expDate { return "Subscription expired: \(localizedTitle) at: \(dateString)" } else { return "Subscription active: \(localizedTitle) until:\(dateString)" } } else { return "Subscription not purchased: \(localizedTitle)" } }

这是我们的开始屏幕
现在转到订阅屏幕。 根据Apple的规定,由于付款屏幕上应包含购买条件的长文字,因此使用ScrollView
是明智的。
var body: some View { ScrollView (showsIndicators: false) { VStack { Text("Get Premium Membership").font(.title) Text("Choose one of the packages above").font(.subheadline) self.purchaseButtons() self.aboutText() self.helperButtons() self.termsText().frame(width: UIScreen.main.bounds.size.width) self.dismissButton() }.frame(width : UIScreen.main.bounds.size.width) }.disabled(self.isDisabled) }
在此示例中,我们创建了两个具有不同字体的文本视图。 此外,所有其他视图均以其自己的方法突出显示。 这样做有以下三个原因:
该代码变得更具可读性和可读性,可供研究。
在撰写本文时,Xcode 11 Beta通常会冻结并且无法编译代码,将部分代码放入函数中有助于编译器。
表明可以将视图分为不同的功能,从而使body
更轻松。
考虑purchaseButtons()
方法:
func purchaseButtons() -> some View {
在这里,我们创建了一个水平堆栈,并在ForEach
循环中创建了一个自定义PurchaseButton
,将产品和回调块传递到其中。
PurchaseButton
类:
struct PurchaseButton : View { var block : SuccessBlock! var product : SKProduct! var body: some View { Button(action: { self.block() }) { Text(product.localizedPrice()).lineLimit(nil).multilineTextAlignment(.center).font(.subheadline) }.padding().frame(height: 50).scaledToFill().border(Color.blue, width: 1) } }
这是一个普通按钮,用于存储和调用创建对象时传递的块。 对其应用了舍入笔触。 我们在localizedPrice()
方法中以文本形式显示产品的价格和订阅期的持续时间。
订阅购买的实现方式如下:
func purchaseProduct(skproduct : SKProduct){ print("did tap purchase product: \(skproduct.productIdentifier)") isDisabled = true IAPManager.shared.purchaseProduct(product: skproduct, success: { self.isDisabled = false ProductsStore.shared.handleUpdateStore() self.dismiss() }) { (error) in self.isDisabled = false ProductsStore.shared.handleUpdateStore() } }
如您所见,购买handleUpdateStore
,将handleUpdateStore
方法,并在该方法的帮助下发送通知以更新ContentView
。 这是为了确保在隐藏模式屏幕时ContentView
订阅ContentView
状态。 dismiss
方法隐藏模式窗口。
由于SwiftUI是声明性框架,因此无法像往常一样实现隐藏模式窗口。 我们必须在presentationMode
变量的包装器上调用dismiss()
方法,并使用@Environment
属性进行声明:
struct PurchaseView : View { @State private var isDisabled : Bool = false @Environment(\.presentationMode) var presentationMode private func dismiss() { self.presentationMode.wrappedValue.dismiss() } func dismissButton() -> some View { Button(action: { self.dismiss() }) { Text("Not now").font(.footnote) }.padding() } ...
presentationMode
变量是“ 环境值”的一部分-特殊的全局方法和属性集。 在SwiftUI中,几乎所有动作都是在更改变量的值时发生的,您无法在运行时按字面意义做任何事情-一切都是预先限定的。 为了在运行时执行某些操作,您需要使用包装器。

订阅购买画面
结论
希望本文对您有所帮助。 当开发人员使用其最新技术时,Apple会喜欢。 如果您使用SwiftUI发布适用于iOS 13的应用程序,则很有可能成为臭名昭著的Apple。 因此,不要害怕新技术-使用它们。 您可以在此处下载完整的项目代码。
是否想在10分钟内在iOS应用中实现订阅? 整合Apphud并:
- 仅使用一种方法进行购买;
- 自动跟踪每个用户的订阅状态;
- 轻松整合订阅优惠
- 将订阅事件发送到Amplitude,Mixpanel,Slack和Telegram,并考虑到用户的本地货币;
- 降低应用程序的客户流失率并返回未订阅的用户。
读什么?