簡體   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