SwiftUI e assinaturas renováveis ​​automaticamente

imagem


Oi Denis conectado da Apphud - um serviço para a análise de assinaturas renováveis ​​para aplicativos iOS.


Como você sabe, na WWDC 2019, a Apple anunciou sua nova estrutura declarativa SwiftUI. Neste artigo, tentarei explicar como usar o SwiftUI para fazer telas de pagamento e implementar a funcionalidade de assinaturas renováveis ​​automaticamente.


Se você ainda não conhece o SwiftUI, pode ler um pequeno artigo introdutório . E se você quiser saber mais sobre assinaturas e como implementá-las corretamente, leia este artigo .

Você precisa do Xcode 11 para funcionar . Crie um novo projeto e verifique se há uma marca de seleção ao lado de "Usar SwiftUI".


SwiftUI é uma estrutura para escrever uma interface e, portanto, não podemos criar um gerente de compras usando-a. Mas não escreveremos para o nosso gerente, mas usaremos uma solução pronta, que complementamos com nosso código. Você pode usar, por exemplo, SwiftyStoreKit . No nosso exemplo, usaremos a classe do artigo anterior .


Os produtos serão inicializados na tela principal, a data de vencimento de nossas assinaturas e o botão para mudar para a tela de compra também serão exibidos lá.


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 a classe SceneDelegate . Nele, criamos uma classe Singleton ProductsStore , na qual os produtos são inicializados. Depois disso, crie nosso ContentView raiz e especifique singleton como um parâmetro de entrada.
Considere a 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 } } } 

Essa classe pequena, um tipo de "complemento" no IAPManager , serve para atualizar o ContentView ao atualizar a lista de produtos. A classe ProductsStore suporta o protocolo ObservableObject .


O que são ObservableObject e @Published ?


ObservableObject é um protocolo especial para observação de objetos e rastreamento de alterações em suas propriedades. As propriedades devem ser marcadas com o atributo @Published . No exemplo, uma notificação é enviada quando a matriz de products alterada, mas você pode adicionar essa notificação para quaisquer métodos e propriedades do objeto.


O carregamento do produto em si pode ser feito de qualquer maneira, mas no final desta solicitação, você deve atribuir a matriz do produto à variável de produtos. Como ouvir as mudanças? Isso é feito usando o parâmetro de chave @ObservedObject :


 @ObservedObject var productsStore : ProductsStore 

Simplificando, isso é algo semelhante ao Centro de Notificação. E para que o seu View aceite essas notificações, você deve ter uma variável desse objeto com o atributo @ObservedObject .


Vamos voltar à lógica da classe ProductsStore . Seu principal objetivo é baixar e armazenar uma lista de produtos. Mas a matriz de produtos já está armazenada no IAPManager , ocorre duplicação. Isso não é bom, mas, primeiro, neste artigo, eu queria mostrar como o binning de objetos é implementado e, segundo, nem sempre é possível alterar a classe final do gerente de compras. Por exemplo, se você usar bibliotecas de terceiros, não poderá adicionar o protocolo ObservableObject e enviar notificações.


Vale ressaltar que, além do atributo @ObservedObject também existe o atributo @State , que ajuda a rastrear alterações em variáveis ​​simples (por exemplo, String ou Int ) e o @EnvironmentObject mais global, que pode atualizar todo o View no aplicativo de uma só vez, sem precisar passar a variável entre os objetos.


Vamos ContentView tela inicial 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() } } } } 

Vamos descobrir o código. Usando o ForEach criamos View texto, cujo número é igual ao número de produtos. Como vinculamos a variável productsStore , a View será atualizada sempre que a matriz de produtos na classe ProductsStore alterada.


O método subscriptionStatus faz parte da SKProduct classe SKProduct e retorna o texto desejado, dependendo da data de validade da assinatura:


 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 é a nossa tela inicial
Esta é a nossa tela inicial


Agora vá para a tela de inscrição. Como, de acordo com as regras da Apple, a tela de pagamento deve conter um texto longo das condições de compra, é aconselhável usar o 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) } 

Neste exemplo, criamos duas visualizações de texto com uma fonte diferente. Além disso, todas as outras visualizações são destacadas em seus próprios métodos. Isso é feito por três razões:


  1. O código se torna mais legível e compreensível para o estudo.


  2. No momento da redação deste artigo, o Xcode 11 Beta geralmente congela e não pode compilar código, e colocar partes do código em funções ajuda o compilador.


  3. Mostre que a vista pode ser transformada em funções separadas, facilitando o body .



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

Aqui, criamos uma pilha horizontal e, em um loop ForEach , criamos um PurchaseButton personalizado, no qual transferimos o produto e o bloco de retorno de chamada.


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

Este é um botão normal que armazena e chama o bloco passado ao criar o objeto. Um traçado de arredondamento é aplicado a ele. Exibimos o preço do produto e a duração do período da assinatura como o texto no método localizedPrice() .


A compra da assinatura é implementada da seguinte maneira:


 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 você pode ver, após a conclusão da compra, o método handleUpdateStore é handleUpdateStore , com a ajuda da qual uma notificação é enviada para atualizar o ContentView . Isso é para garantir que o ContentView status das assinaturas ao ocultar uma tela modal. O método de dismiss oculta a janela modal.


Como o SwiftUI é uma estrutura declarativa, ocultar uma janela modal não é implementada como de costume. Devemos chamar o método de dismiss() no wrapper da variável presentationMode , declarando-o com o 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() } ... 

A variável presentationMode faz parte dos Environment Values - conjuntos especiais de métodos e propriedades globais. No SwiftUI, quase todas as ações ocorrem ao alterar os valores das variáveis, você não pode fazer nada em tempo de execução no sentido literal da palavra - tudo é delimitado com antecedência. E para fazer algo em tempo de execução, você precisa usar wrappers.


Tela de compra de assinatura
Tela de compra de assinatura


Conclusão


Espero que este artigo seja útil para você. A Apple adora quando os desenvolvedores usam sua tecnologia mais recente. Se você lançar um aplicativo para iOS 13 usando o SwiftUI, há uma probabilidade potencial de ser uma Apple infame. Portanto, não tenha medo de novas tecnologias - use-as. Você pode baixar o código completo do projeto aqui .


Deseja implementar assinaturas no seu aplicativo iOS em 10 minutos? Integre o Apphud e:
  • Faça compras usando apenas um método;
  • rastreia automaticamente o status da assinatura de cada usuário;
  • Integre facilmente as ofertas de assinatura
  • enviar eventos de assinatura para Amplitude, Mixpanel, Slack e Telegram, levando em consideração a moeda local do usuário;
  • diminua a taxa de rotatividade de aplicativos e retorne usuários não inscritos.


O que ler?


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


All Articles