По сути, я никогда не реализовал IAP в SwiftUI, особенно с помощью StoreKit 2, поэтому я тщательно следовал этому руководству, чтобы реализовать весь процесс покупки. Однако, учитывая опыт работы с UIKit, у меня есть много сомнений.
Я читал здесь, что вам следует делать это с помощью UserDefaults или iCloud, однако в прошлом я обычно использовал связку ключей.< /p>
Я хочу локально сохранять информацию о том, купил ли пользователь что-то или нет, особенно в отношении подписок, чтобы иметь возможность всегда доставлять купленный контент даже в автономном режиме.
код, который я реализовал в соответствии со связанным руководством, уже предоставляет некоторые функции для этого, но я боюсь, что это работает только при включенном подключении к Интернету. Для справки:
Код: Выделить всё
@MainActor
private func handle(transactionVerification result: VerificationResult ) async {
switch result {
case let.verified(transaction):
guard
let product = self.products.first(where: {
$0.id == transaction.productID
})
else {
return
}
self.purchasedNonConsumables.insert(product)
await transaction.finish()
default:
return
}
}
func listenForTransactions() -> Task {
return Task.detached {
for await result in Transaction.updates {
await self.handle(transactionVerification: result)
}
}
}
Код: Выделить всё
class KeychainManager {
static func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data
] as [String : Any]
SecItemDelete(query as CFDictionary) // Ensures we remove any existing item before adding new one
return SecItemAdd(query as CFDictionary, nil)
}
static func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne
] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as? Data
}
return nil
}
static func saveSubscriptionInfo(key: String, subscriptionInfo: SubscriptionInfo) -> OSStatus {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(subscriptionInfo)
return save(key: key, data: data)
} catch {
print("Failed to encode subscription info: \(error)")
return errSecInternalError // Using a generic internal error code
}
}
static func loadSubscriptionInfo(key: String) -> SubscriptionInfo? {
guard let data = load(key: key) else { return nil }
let decoder = JSONDecoder()
do {
let subscriptionInfo = try decoder.decode(SubscriptionInfo.self, from: data)
return subscriptionInfo
} catch {
print("Failed to decode subscription info: \(error)")
return nil
}
}
}
struct SubscriptionInfo: Codable {
var expiryDate: Date?
var isSubscribed: Bool
}
Подробнее здесь: https://stackoverflow.com/questions/783 ... -swiftui-u
Мобильная версия