
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"
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
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:
Le code devient plus lisible et compréhensible pour l'étude.
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.
Montrez que la vue peut être transformée en fonctions distinctes, facilitant le body
.
Considérez la méthode purchaseButtons()
:
func purchaseButtons() -> some View {
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
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?