繁体   English   中英

如何在 SwiftUI 视图中使用 if/ForEach 来显示 IAP 模式?

[英]How to use if/ForEach in a SwiftUI View to show IAP modal?

我试图在应用程序开始鼓励用户订阅 Pro In-App Purchase 后立即显示一个立即订阅模式视图,所以我使用了.onAppear修饰符,它只有在我想显示模式时才能正常工作应用程序启动的时间。

struct ContentView: View {
    @State private var selection: String? = nil
    @State private var showModal = false
    @ObservedObject var storeManager: StoreManager
        
    var body: some View {
        NavigationView {
            VStack {
                // Contents Here
            }
        }
        .onAppear {
            self.selection = "Pro"
            self.showModal.toggle()
        }
        .sheet(isPresented: $showModal) {
            if self.selection == "Pro" {
                Pro(showModal: self.$showModal, storeManager: self.storeManager)
                    .onAppear(perform: {
                        SKPaymentQueue.default().add(storeManager)
                    })
            }
        }
    }
}

现在,当我只想向尚未订阅 Pro IAP 的人显示模式时,问题就开始了,因此我.onAppear修改为:

        .onAppear {
            ForEach(storeManager.myProducts, id: \.self) { product in
                VStack {
                    if !UserDefaults.standard.bool(forKey: product.productIdentifier) {
                        self.selection = "Pro"
                        self.showModal.toggle()
                    }
                }
            }
        }

但是, ifForEach似乎不能顺利地与结构和视图一起使用。 在我的情况下我应该如何使用它们?

更新:

根据答案,我更改了.onAppear内的循环以使代码符合 SwiftUI 要求:

.onAppear {
    storeManager.myProducts.forEach { product in
    // Alternatively, I can use (for in) loop:
    // for product in storeManager.myProducts {
        if !UserDefaults.standard.bool(forKey: product.productIdentifier) {
            self.selection = "Pro"
            self.showModal.toggle()
        }
    }
}

现在,错误已经消失,但启动时不显示模式。

我发现问题是, storeManager.myProducts没有加载到.onAppear修饰符中,而当我将相同的循环放入按钮而不是 .onAppear 时,它会正确加载,有什么想法吗? 为什么 onAppear 不加载 IAP? 当视图加载时,我应该在哪里放置代码以使模式运行?

更新 2:

这是一个最小的可重现示例:

应用程序:

import SwiftUI

@main
struct Reprod_SOFApp: App {
    @StateObject var storeManager = StoreManager()
    let productIDs = ["xxxxxxxxxxxxxxxxxxxxx"]

    var body: some Scene {
        DocumentGroup(newDocument: Reprod_SOFDocument()) { file in
            ContentView(document: file.$document, storeManager: storeManager)
                .onAppear() {
                    storeManager.getProducts(productIDs: productIDs)
                }
        }
    }
}

内容视图:

import SwiftUI
import StoreKit

struct ContentView: View {
    @Binding var document: Reprod_SOFDocument
    @State private var selection: String? = nil
    @State private var showModal = false
    @ObservedObject var storeManager: StoreManager
    var test = ["t"]

    var body: some View {
        TextEditor(text: $document.text)
            .onAppear {
                // storeManager.myProducts.forEach(id: \.self) { product in
                // Alternatively, I can use (for in) loop:
                 for i in test {
                     if !i.isEmpty {
                        self.selection = "Pro"
                        self.showModal.toggle()
                     }
                 }
            }
            .sheet(isPresented: $showModal) {
                if self.selection == "Pro" {
                    Modal(showModal: self.$showModal, storeManager: self.storeManager)
                        .onAppear(perform: {
                                SKPaymentQueue.default().add(storeManager)
                        })
                }
            }
    }
}

模态:

import SwiftUI
import StoreKit

struct  Modal: View {
    @Binding var showModal: Bool
    @ObservedObject var storeManager: StoreManager

    var body: some View {
        Text("hello world")
    }
}

店经理:

import Foundation
import StoreKit

class StoreManager: NSObject, ObservableObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
    @Published var myProducts = [SKProduct]()
    var request: SKProductsRequest!
    @Published var transactionState: SKPaymentTransactionState?
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchasing:
                transactionState = .purchasing
            case .purchased:
                UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
                queue.finishTransaction(transaction)
                transactionState = .purchased
            case .restored:
                UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
                queue.finishTransaction(transaction)
                transactionState = .restored
            case .failed, .deferred:
                print("Payment Queue Error: \(String(describing: transaction.error))")
                    queue.finishTransaction(transaction)
                    transactionState = .failed
                    default:
                    queue.finishTransaction(transaction)
            }
        }
    }

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        print("Did receive response")
        
        if !response.products.isEmpty {
            for fetchedProduct in response.products {
                DispatchQueue.main.async {
                    self.myProducts.append(fetchedProduct)
                }
            }
        }
        
        for invalidIdentifier in response.invalidProductIdentifiers {
            print("Invalid identifiers found: \(invalidIdentifier)")
        }
    }
        
    func getProducts(productIDs: [String]) {
        print("Start requesting products ...")
        let request = SKProductsRequest(productIdentifiers: Set(productIDs))
        request.delegate = self
        request.start()
    }
    
    func request(_ request: SKRequest, didFailWithError error: Error) {
        print("Request did fail: \(error)")
    }
    
    func purchaseProduct(product: SKProduct) {
        if SKPaymentQueue.canMakePayments() {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
        } else {
            print("User can't make payment.")
        }
    }
    
    func restoreProducts() {
        print("Restoring products ...")
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
                      
}

这是指向最小可重现示例的链接

您正在尝试在onAppear中使用View代码,它不接受View - 它应该只是常规的命令式 Swift 代码。 所以,你可能想要这样的东西:

.onAppear {
  storeManager.myProducts.forEach { product in 
    if !UserDefaults.standard.bool(forKey: product.productIdentifier) {
      self.selection = "Pro"
      self.showModal.toggle() //maybe change this to = true -- toggle could *close* it again if there were multiple items that qualified
    }
  } 
}

小心,在 your.onAppear{} 上,它应该是 Swift 代码而不是 SwiftUI(ForEach,VStack 是视图结构)

我建议将逻辑分离到一个视图模型中,您只需管理一个已识别的 object 即可显示您的专业模式。

struct ContentView: View {
    @ObservedObject var viewModel: ContentViewModel
    var body: some View {
        Text("Hello")
            .onAppear(perform: viewModel.fetchStatus)
            .sheet(item: $viewModel.carrier) { carrier in
                ModalView(storeManager: carrier.storeManager)
            }
    }
}

class ContentViewModel: ObservableObject {
    @Published var carrier: ModalObject?
    
    private let storeManager: StoreManager

    func fetchStatus() {
        // do something asynchronous like
        storeManager.fetchProducts() { [self] products in
            if !products.contains(proProducts) {
                self.carrier = ModalObject(storeManager: self.storeManager)
            }
        }
    }
}

struct ModalObject: Identifiable {
    var id = UUID()
    let storeManager: StoreManager
}

我只是写了没有编译,请检查你的xcode。

您可以更改selectionshowModal的初始值,而不是使用.onAppear修饰符来显示模式:

@State private var selection: String? = "Pro"
@State private var showModal = !UserDefaults.standard.bool(forKey: "xxxxxxxxxxxxxxxxxxxxx") ? true : false
// Write your product identifier instead of "xxxxxxxxxxxxxxxxxxxxx"

这样,模式视图将在内容视图加载后立即显示。

注意:对于showModal ,我应用了条件if而不是简单地true ,因为您说您只想向尚未订阅 Pro IAP 的人显示模式。

一种方法是使用 model 来跟踪您何时询问用户。

import SwiftUI
struct AskedModel: Codable{
    var id: String
    //Date the user was last asked whatever the id represents
    var lastAsked: Date?
    //Sets the `lastAsked` variable to today
    mutating func setAskedToday(){
        lastAsked = Date()
    }
    //Checks if the user was last asked `n` days ago
    //The default is 30 days
    func askedNDaysAgo(n: Int = 30) -> Bool{
        if lastAsked != nil{
            return Calendar.current.dateComponents([.day], from: Date(), to: lastAsked!).day ?? 0 >= n
        }else{
            return false
        }
    }
    //Saves the AskedModel to UserDefaluts
    func saveToUserDefaults(){
        let defaults = UserDefaults.standard
        let encoder = JSONEncoder()
        do{
            let encoded = try encoder.encode(self)
            defaults.set(encoded, forKey: self.id + "-asked")
            
        }catch{
            print(error)
        }
        
    }
    //Gets the AskedModel represented by the id provided from UserDefaults
    static func getFromUserDefaults(id: String) -> AskedModel{
        var result = AskedModel(id: id)
        let defaults = UserDefaults.standard
        
        if let saved = defaults.object(forKey: id + "-asked") as? Data{
            let decoder = JSONDecoder()
            
            do{
                result = try decoder.decode(AskedModel.self, from: saved)
            }catch{
                print(error)
            }
        }
        return result
        
    }
}

然后在视图 model 中,您可以根据上次询问用户的时间和商店经理来展示表格

class ProViewModel: ObservableObject{
    var productIdentifier: String = "com.yourCompany.Pro"
    var proAsked: AskedModel{
        get{
            AskedModel.getFromUserDefaults(id: productIdentifier)
        }
        set{
            newValue.saveToUserDefaults()
            objectWillChange.send()
        }
    }
    @Published var showSheet: Bool = false
    
    init(){
        showSheet = shouldAsk()
    }
    func shouldAsk() -> Bool{
        var result = !isPurchased()
        print(result)
        if result{
            result = !proAsked.askedNDaysAgo()
            print(result)
        }
        return result
    }
    func isPurchased() -> Bool{
        //Ask Store if purchased
        false
    }
}

然后在显示ProViewonDismiss调用setAskedToday的工作sheet上使用showSheet变量,以便 model 将更新为今天的日期。

struct ConditionalView: View {
    @StateObject var proVM: ProViewModel = ProViewModel()
    var body: some View {
        Text("Hello, World!")
            .sheet(isPresented: $proVM.showSheet, onDismiss: {
                proVM.proAsked.setAskedToday()
            }, content: {
                Text("ProView")
            })
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM