EzIAP - 一个针对 iOS 的内购框架
一个简化 iOS 项目内购集成的框架。使用 Swift 4 编写。
已更新以与 Cocoapods 一同使用。
概述
此框架简化了在您的 iOS Swift 应用程序中设置和处理内购的过程,并由 Cocoapods 支持。
pod 'EzIAP'
项目包括一个示例应用程序,演示如何在您的应用程序中使用该框架。您可以通过项目中的 'iap_test' 计划运行它。
项目与静态产品(您不打算更改的硬编码到您的应用程序中的产品)以及动态产品(从您的远程 API 或 json 文件加载的产品)兼容。在示例应用程序中提供了如何使用这两者的示例。请参阅下面的“使用”部分以获取示例。
请注意,测试应用程序将验证产品标识符,但产品标识符仅为占位符,因此当您运行项目时验证将失败。您需要在验证和购买/恢复测试成功之前更新或在自己的项目中使用。
有以下三种方式可以用您自己的产品标识符进行测试。
- 将测试项目的包标识符更改为自己的包标识符,并更新示例以使用您自己的产品标识符,将此项目作为测试床使用。
- 将文件复制到您自己的项目中,并更新产品标识符进行测试。
- 通过 Cocoapods 在您自己的项目中设置框架。
需求
- iOS 10.0+
- Xcode 9
- Swift 4
用法
您可以在项目中使用此框架的两种方式。
Cocoapods
最简单的方法是使用 cocoapods。首先确保安装了 cocoapods,然后打开终端并切换到项目根目录。
然后运行
pod init
这将在项目根目录下生成一个 Podfile 文件。打开该文件,添加 'EzIAP' 的依赖项。您的 podfile 应该看起来像这样
# Uncomment the next line to define a global platform for your project
platform :ios, '10.0'
source 'https://github.com/CocoaPods/Specs.git'
# Example target
target 'MY_APP_TARGET_NAME_HERE' do
use_frameworks!
pod 'EzIAP'
end
设置好 podfile 后,在终端运行 pod install
。当它完成时,关闭任何打开的 Xcode 项目,并使用新生成的 xcworkspace 打开您的项目。
手动
如果您想手动使用源文件,可以将 'EzIAP/Source/' 目录中的 'iTunesStore' 目录复制出来,并将整个目录放置到您的项目中。
-- 从现在开始,无论通过 Cocoapods 还是手动设置依赖项,设置都是相同的。 --
通过 Cocoapods 或手动设置依赖项后,您需要设置 GeneralStore
类的实例(或编写您自己的自定义版本,实现 'Store' 协议,如果您想这样做,请参见 'DemoStore' 类作为示例)。
您可以像这样设置您的 ViewModel 以使用存储器(这是 HomeViewModel 的一部分)
import EzIAP
// setup store to handle purchase processing
var store: Store!
init() {
store = setupStore()
}
internal func setupStore() -> GeneralStore {
let store = GeneralStore()
store.onTransactionInitiated = { status in
self.showSpinner(true)
}
store.onTransactionComplete = { status in
self.showSpinner(false)
if let productID = status.transaction?.payment.productIdentifier {
// Store product id in UserDefaults or some other method of tracking purchases
UserDefaults.standard.set(true , forKey: productID)
UserDefaults.standard.synchronize()
}
}
store.onTransactionCancelled = { status in
self.showSpinner(false)
}
store.onTransactionFailed = { status in
self.showSpinner(false)
if let error = status.error {
print("Purchase Error: \(error.localizedDescription)")
}
}
return store
}
使用最简单的实现是样本应用程序中的 StaticProductsViewModel 类。它在初始化时接收一个 Store
对象。《HomeViewModel》创建 GeneralStore
的实例,并将其传递给它创建的所有 ViewModel。由于 HomeViewModel 是应用程序中的基本 ViewModel,因此所有其他 ViewModel 都使用相同的 Store。
HomeViewModel 以这种方式提供模型供子控制器使用
// MARK: View Model Vending
extension HomeViewModel {
func staticViewModel() -> StaticProductsViewModel {
return StaticProductsViewModel(store: store)
}
func dynamicViewModel() -> DynamicProductsViewModel {
return DynamicProductsViewModel(store: store)
}
}
以下是完整的实现
import Foundation
import EzIAP
class StaticProductsViewModel {
// setup to handle purchase processing
var store: Store
// this will contain our static list of products
internal var products: [Product] = []
init(store: Store) {
self.store = store
// setup product list on initialization
setupProducts()
}
func setupProducts() {
// setup products list with static list of products
// we already know what all products will be so...
products = [
MyProduct(identifier: "com.myapp.static.id1", name: "Static Product 1", price: 1.99),
MyProduct(identifier: "com.myapp.static.id2", name: "Static Product 2", price: 2.99),
MyProduct(identifier: "com.myapp.static.id3", name: "Static Product 3", price: 3.99),
MyProduct(identifier: "com.myapp.static.id4", name: "Static Product 4", price: 4.99),
MyProduct(identifier: "com.myapp.static.id5", name: "Static Product 5", price: 5.99)
]
// validate these product identifiers with the app store...
store.validateProducts(products)
}
}
extension StaticProductsViewModel: ProductSelectionDelegate {
// On product selection tell the store to purchase the selected product
func selected(product: Product) {
store.purchaseProduct(product)
}
}
// MARK: Data Access Functions for View Controller
extension StaticProductsViewModel {
func productCount() -> Int {
return products.count
}
func item(at index: Int) -> Product {
return products[index]
}
}
对于更复杂的示例,您可以在样本项目中查看 DynamicProductsViewModel
类。
它使用一个虚拟API类来“获取”产品列表。对于演示,它从一个json文件中加载数产品并返回模型对象。
以下是DynamicProductsViewModel
的实现
import Foundation
import EzIAP
/// This class represents a use case where products are fetched from a remote server,
/// or where the product list may vary independent of builds submitted to the App Store.
class DynamicProductsViewModel {
// setup store to handle purchase processing
var store: Store
// This is a dummy api for demo purposes
internal var api = RemoteApi()
// this will contain our products when fetched/loaded
internal var products = [Product]()
// this handler will run when the product list changes, setter should handle view updates/etc
var onProductListUpdate: (() -> Void)?
init(store: Store) {
self.store = store
}
// fetch list of products from remote server
func fetchProductList() {
// TODO: connect to remote server...
api.fetchProducts { [weak self] (products, error) in
guard let products = products, error == nil else {
return
}
// assign or append to products array as needed
self?.products = products
// this validates the products with the App Store.
self?.store.validateProducts(products)
// run closure to notify of list update
self?.onProductListUpdate?()
}
}
func tappedCell(indexPath: IndexPath) {
// in this case tapping a cell is a purchase request... so trigger a purchase call
store.purchaseProduct(products[indexPath.row])
}
}
// MARK: data access functions for the view controller
extension DynamicProductsViewModel {
func sectionCount() -> Int {
return 1
}
func rowCount(section: Int) -> Int {
return products.count
}
func item(at indexPath: IndexPath) -> Product {
return products[indexPath.row]
}
}