[英]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()
}
}
}
}
但是, if
和ForEach
似乎不能顺利地与结构和视图一起使用。 在我的情况下我应该如何使用它们?
更新:
根据答案,我更改了.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。
您可以更改selection
和showModal
的初始值,而不是使用.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
}
}
然后在显示ProView
和onDismiss
调用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.