【问题标题】:How to use if/ForEach in a SwiftUI View to show IAP modal?如何在 SwiftUI 视图中使用 if/ForEach 来显示 IAP 模式?
【发布时间】:2022-01-15 15:05:47
【问题描述】:

我试图在应用开始后立即显示 Subscribe Now 模式视图,以鼓励用户订阅 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 的人显示模式 only 时,问题就开始了,因此我将.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()
    }
                      
}

Here is a link to Minimal Reproducible Example

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    不使用.onAppear修饰符来显示模态,你可以改变selectionshowModal的初始值:

    @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 的用户。

    【讨论】:

      【解决方案2】:

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

      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。

      【讨论】:

        【解决方案3】:

        您的.onAppear{} 应该是 Swift 代码而不是 SwiftUI (ForEach, VStack)。 VStack 是视图结构。

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-04-19
        • 1970-01-01
        • 1970-01-01
        • 2019-10-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多