SwiftUI y suscripciones de renovación automática

imagen


Hola Connected Denis de Apphud : un servicio para el análisis de suscripciones renovables para aplicaciones iOS.


Como saben, en WWDC 2019, Apple anunció su nuevo marco declarativo SwiftUI. En este artículo trataré de decir cómo usar SwiftUI para hacer pantallas de pago e implementar la funcionalidad de suscripciones auto-renovables.


Si aún no está familiarizado con SwiftUI, puede leer un breve artículo introductorio . Y si desea obtener más información sobre las suscripciones y cómo implementarlas correctamente, lea este artículo .

Necesitas Xcode 11 para trabajar . Cree un nuevo proyecto y asegúrese de que haya una marca de verificación junto a "Usar SwiftUI".


SwiftUI es un marco para escribir una interfaz y, por lo tanto, no podemos crear un gerente de compras que lo use. Pero no escribiremos a nuestro gerente, sino que utilizaremos una solución preparada, que complementaremos con nuestro código. Puede usar, por ejemplo, SwiftyStoreKit . En nuestro ejemplo, usaremos la clase de nuestro artículo anterior .


Los productos se inicializarán en la pantalla principal, la fecha de vencimiento de nuestras suscripciones y el botón para cambiar a la pantalla de compra también se mostrarán allí.


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() } 

Considere la clase SceneDelegate . En él, creamos una tienda de ProductsStore clase singleton, en la que se inicializan los productos. Después de eso, cree nuestra ContentView raíz y especifique singleton como parámetro de entrada.
Considere la clase ProductsStore :


 class ProductsStore : ObservableObject { static let shared = ProductsStore() @Published var products: [SKProduct] = [] @Published var anyString = "123" // little trick to force reload ContentView from PurchaseView by just changing any Published value func handleUpdateStore(){ anyString = UUID().uuidString } func initializeProducts(){ IAPManager.shared.startWith(arrayOfIds: [subscription_1, subscription_2], sharedSecret: shared_secret) { products in self.products = products } } } 

Esta pequeña clase, una especie de "complemento" sobre el IAPManager , sirve para actualizar ContentView al actualizar la lista de productos. La clase ProductsStore admite el protocolo ObservableObject .


¿Qué son ObservableObject y @Published ?


ObservableObject es un protocolo especial para observar objetos y rastrear cambios en sus propiedades. Las propiedades deben estar marcadas con el atributo @Published . En el ejemplo, se envía una notificación cuando cambia la matriz de products , pero puede agregar esta notificación para cualquier método y propiedades del objeto.


La carga del producto en sí se puede hacer de cualquier manera, pero al final de esta solicitud debe asignar la matriz de productos a la variable de productos. ¿Cómo escuchar los cambios? Esto se hace usando el parámetro clave @ObservedObject :


 @ObservedObject var productsStore : ProductsStore 

En pocas palabras, esto es algo similar al Centro de notificaciones. Y para que su View acepte estas notificaciones, debe tener una variable de este objeto con el atributo @ObservedObject .


Volvamos a la lógica de la clase ProductsStore . Su objetivo principal es descargar y almacenar una lista de productos. Pero la gama de productos ya está almacenada en IAPManager , se produce una duplicación. Esto no es bueno, pero, en primer lugar, en este artículo quería mostrarle cómo se implementa la agrupación de objetos y, en segundo lugar, no siempre es posible cambiar la clase final del administrador de compras. Por ejemplo, si usa bibliotecas de terceros, no podrá agregar el protocolo ObservableObject y enviar notificaciones.


Vale la pena señalar que, además del atributo @ObservedObject también existe el atributo @State , que ayuda a rastrear los cambios en variables simples (por ejemplo, String o Int ) y el @EnvironmentObject más global, que puede actualizar todas las View de la aplicación a la vez sin tener que pasar la variable entre los objetos.


Pasemos ContentView pantalla de inicio de 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() } } } } 

Averigüemos el código. Usando ForEach creamos View textuales, cuyo número es igual al número de productos. Como vinculamos la variable productsStore , la View se actualizará cada vez que cambie la matriz de productos en la clase ProductsStore .


El método SKProduct es parte de la SKProduct clase SKProduct y devuelve el texto deseado según la fecha de vencimiento de la suscripción:


 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)" } } 

Esta es nuestra pantalla de inicio
Esta es nuestra pantalla de inicio


Ahora ve a la pantalla de suscripción. Dado que, de acuerdo con las reglas de Apple, la pantalla de pago debe tener un texto largo de las condiciones de compra, sería conveniente utilizar 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) } 

En este ejemplo, creamos dos vistas de texto con una fuente diferente. Además, todas las demás vistas se destacan en sus propios métodos. Esto se hace por tres razones:


  1. El código se vuelve más legible y comprensible para su estudio.


  2. En el momento de escribir esto, Xcode 11 Beta a menudo se congela y no puede compilar código, y poner partes del código en funciones ayuda al compilador.


  3. Muestre que la vista se puede convertir en funciones separadas, facilitando el body .



Considere el método purchaseButtons() :


 func purchaseButtons() -> some View { // remake to ScrollView if has more than 2 products because they won't fit on screen. HStack { Spacer() ForEach(ProductsStore.shared.products, id: \.self) { prod in PurchaseButton(block: { self.purchaseProduct(skproduct: prod) }, product: prod).disabled(IAPManager.shared.isActive(product: prod)) } Spacer() } } 

Aquí creamos una pila horizontal y en un bucle ForEach creamos un PurchaseButton personalizado, en el que transferimos el producto y el bloque de devolución de llamada.


Clase 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) } } 

Este es un botón normal que almacena y llama al bloque pasado al crear el objeto. Se le aplica un trazo de redondeo. Mostramos el precio del producto y la duración del período de suscripción como texto en el método localizedPrice() .


La compra de suscripción se implementa de la siguiente manera:


 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() } } 

Como puede ver, una vez handleUpdateStore la compra, se handleUpdateStore método handleUpdateStore , con la ayuda de la cual se envía una notificación para actualizar ContentView . Esto es para garantizar que ContentView estado de las suscripciones al ocultar una pantalla modal. El método de dismiss oculta la ventana modal.


Dado que SwiftUI es un marco declarativo, ocultar una ventana modal no se implementa como de costumbre. Debemos llamar al método de dismiss() en el contenedor de la variable presentationMode , declarándolo con el atributo @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() } ... 

La variable presentationMode es parte de los valores del entorno : conjuntos especiales de métodos y propiedades globales. En SwiftUI, casi todas las acciones ocurren cuando se cambian los valores de las variables, no se puede hacer nada en tiempo de ejecución en el sentido literal de la palabra: todo está limitado de antemano. Y para hacer algo en tiempo de ejecución, debe usar envoltorios.


Pantalla de compra de suscripción
Pantalla de compra de suscripción


Conclusión


Espero que este artículo te sea útil. A Apple le encanta cuando los desarrolladores usan su última tecnología. Si lanzas una aplicación para iOS 13 con SwiftUI, existe la posibilidad de ser una infame Apple. Así que no tengas miedo de las nuevas tecnologías: úsalas. Puede descargar el código completo del proyecto aquí .


¿Desea implementar suscripciones en su aplicación iOS en 10 minutos? Integra Apphud y:
  • Haga compras usando solo un método;
  • rastrea automáticamente el estado de la suscripción de cada usuario;
  • Integre ofertas de suscripción fácilmente
  • enviar eventos de suscripción a Amplitude, Mixpanel, Slack y Telegram teniendo en cuenta la moneda local del usuario;
  • disminuir la tasa de abandono en las aplicaciones y devolver usuarios no suscritos.


Que leer


Source: https://habr.com/ru/post/458116/


All Articles