[英]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.