Abonnements SwiftUI et auto-renouvelables

image


Salut Connected Denis d' Apphud - un service pour l'analyse des abonnements renouvelables pour les applications iOS.


Comme vous le savez, lors de la WWDC 2019, Apple a annoncé son nouveau cadre déclaratif SwiftUI. Dans cet article, je vais essayer de vous expliquer comment utiliser SwiftUI pour créer des écrans de paiement et implémenter la fonctionnalité des abonnements auto-renouvelables.


Si vous n'êtes pas déjà familier avec SwiftUI, vous pouvez lire un court article d'introduction . Et si vous souhaitez en savoir plus sur les abonnements et comment les implémenter correctement, lisez cet article .

Vous avez besoin de Xcode 11 pour fonctionner . Créez un nouveau projet et assurez-vous qu'il y a une coche à côté de «Utiliser SwiftUI».


SwiftUI est un cadre pour écrire une interface, et donc nous ne pouvons pas créer un responsable des achats en l'utilisant. Mais nous n'écrirons pas notre responsable, mais utiliserons une solution toute faite, que nous compléterons avec notre code. Vous pouvez utiliser, par exemple, SwiftyStoreKit . Dans notre exemple, nous utiliserons la classe de notre article précédent .


Les produits seront initialisés sur l'écran principal, la date d'expiration de nos abonnements et le bouton pour passer à l'écran d'achat y seront également affichés.


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

Considérez la classe SceneDelegate . Nous y créons un ProductsStore classe singleton, dans lequel les produits sont initialisés. Après cela, créez notre ContentView racine et spécifiez singleton comme paramètre d'entrée.
Considérez la classe 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 } } } 

Cette petite classe, une sorte de «module complémentaire» sur l' IAPManager , sert à mettre à jour ContentView lors de la mise à jour de la liste des produits. La classe ProductsStore prend en charge le protocole ObservableObject .


Que sont ObservableObject et @Published ?


ObservableObject est un protocole spécial pour observer des objets et suivre les changements dans ses propriétés. Les propriétés doivent être marquées avec l'attribut @Published . Dans l'exemple, une notification est envoyée lorsque le tableau de products change, mais vous pouvez ajouter cette notification pour toutes les méthodes et propriétés de l'objet.


Le chargement du produit lui-même peut être effectué de n'importe quelle manière, mais à la fin de cette demande, vous devez affecter la gamme de produits à la variable produits. Comment écouter les changements? Cela se fait à l'aide du paramètre clé @ObservedObject :


 @ObservedObject var productsStore : ProductsStore 

Autrement dit, c'est quelque chose de similaire au Centre de notification. Et pour que votre View accepte ces notifications, vous devez avoir une variable de cet objet avec l'attribut @ObservedObject .


Revenons à la logique de la classe ProductsStore . Son objectif principal est de télécharger et de stocker une liste de produits. Mais la gamme de produits est déjà stockée dans IAPManager , une duplication se produit. Ce n'est pas bon, mais, premièrement, dans cet article, je voulais vous montrer comment le binning d'objet est implémenté, et, deuxièmement, il n'est pas toujours possible de changer la classe finie du gestionnaire d'achat. Par exemple, si vous utilisez des bibliothèques tierces, vous ne pourrez pas ajouter le protocole ObservableObject et envoyer des notifications.


Il convient de noter qu'en plus de l'attribut @ObservedObject il existe également l'attribut @State , qui permet de suivre les modifications des variables simples (par exemple, String ou Int ) et le @EnvironmentObject plus global, qui peut mettre à jour toutes les View dans l'application en même temps sans avoir à passer la variable entre les objets.


Passons ContentView écran de démarrage 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() } } } } 

Voyons le code. En utilisant ForEach nous créons des View textuelles, dont le nombre est égal au nombre de produits. Puisque nous lions la variable productsStore , la View sera mise à jour chaque fois que le tableau de produits de la classe ProductsStore change.


La méthode subscriptionStatus fait partie de l' SKProduct classe SKProduct et renvoie le texte souhaité en fonction de la date d'expiration de l'abonnement:


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

Ceci est notre écran de démarrage
Ceci est notre écran de démarrage


Allez maintenant à l'écran d'abonnement. Étant donné que, selon les règles d'Apple, l'écran de paiement doit comporter un long texte des conditions d'achat, il serait judicieux d'utiliser 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) } 

Dans cet exemple, nous avons créé deux vues de texte avec une police différente. De plus, toutes les autres vues sont mises en évidence dans leurs propres méthodes. Cela se fait pour trois raisons:


  1. Le code devient plus lisible et compréhensible pour l'étude.


  2. Au moment d'écrire ces lignes, Xcode 11 Beta se bloque souvent et ne peut pas compiler de code, et mettre des parties du code dans des fonctions aide le compilateur.


  3. Montrez que la vue peut être transformée en fonctions distinctes, facilitant le body .



Considérez la méthode 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() } } 

Ici, nous créons une pile horizontale et dans une boucle ForEach nous créons un ForEach PurchaseButton personnalisé, dans lequel nous transférons le produit et le bloc de rappel.


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

Il s'agit d'un bouton normal qui stocke et appelle le bloc passé lors de la création de l'objet. Un trait d'arrondi lui est appliqué. Nous affichons le prix du produit et la durée de la période d'abonnement sous forme de texte dans la méthode localizedPrice() .


L'achat d'abonnement est implémenté comme suit:


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

Comme vous pouvez le voir, une fois l'achat handleUpdateStore , la méthode handleUpdateStore est handleUpdateStore , à l'aide de laquelle une notification est envoyée pour mettre à jour ContentView . Cela permet de garantir que ContentView état des abonnements lors du masquage d'un écran modal. La méthode de dismiss masque la fenêtre modale.


Étant donné que SwiftUI est un cadre déclaratif, masquer une fenêtre modale n'est pas implémenté comme d'habitude. Nous devons appeler la méthode @Environment dismiss() sur le wrapper de la variable presentationMode , en la déclarant avec l'attribut @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 fait partie des valeurs d'environnement - ensembles spéciaux de méthodes et de propriétés globales. Dans SwiftUI, presque toutes les actions se produisent lors de la modification des valeurs des variables, vous ne pouvez rien faire pendant l'exécution au sens littéral du mot - tout est limité à l'avance. Et pour faire quelque chose pendant l'exécution, vous devez utiliser des wrappers.


Écran d'achat d'abonnement
Écran d'achat d'abonnement


Conclusion


J'espère que cet article vous sera utile. Apple aime quand les développeurs utilisent sa dernière technologie. Si vous publiez une application pour iOS 13 à l'aide de SwiftUI, il y a une probabilité potentielle d'être un tristement célèbre Apple. N'ayez donc pas peur des nouvelles technologies - utilisez-les. Vous pouvez télécharger le code de projet complet ici .


Vous souhaitez mettre en œuvre des abonnements dans votre application iOS en 10 minutes? Intégrez Apphud et:
  • Faites des achats en utilisant une seule méthode;
  • suivre automatiquement l'état de l'abonnement de chaque utilisateur;
  • Intégrez facilement les offres d'abonnement
  • envoyer des événements d'abonnement à Amplitude, Mixpanel, Slack et Telegram en tenant compte de la devise locale de l'utilisateur;
  • diminuer le taux de désabonnement dans les applications et renvoyer les utilisateurs non abonnés.


Que lire?


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


All Articles