完整的苹果内购in-purchase实例-采用object-c语言使用苹果的Storyboard开发模式的完整购买实例
引言
此前 写了 object-c的实例,但是交付时候发现客户采用的是Storyboard开发模式,因此我们只能单独再写Storyboard开发模式的代码。
项目开源地址:
内容如下
首先单独去建立了内购的密钥

密钥的ID为Q9W29CG749 对应的 app 包名 Liu.GameTest
密钥放到本地
对应的内购商品
产品参考名称: LiuAppGame1Item1 产品id: 001Liu
产品参考名称: LiuAppGame1Item2 产品id: 002Liu
产品参考名称: LiuAppGame1Item3 产品id: 003Liu
3个商品对应三种金额,对应不同的价格。
已创建AppDelegate.h文件,现在创建AppDelegate.m文件来实现应用程序的生命周期管理。

创建ViewController.m文件,实现内购的核心逻辑,包括加载商品信息、处理购买请求和交易结果等功能。
创建Main.storyboard文件,实现内购界面的UI设计,包括商品信息展示和购买按钮。

创建的结构目录要符合常规项目需要,最后我会放出结构。
-
- AppDelegate.h 和 AppDelegate.m :应用程序委托类
- ViewController.h 和 ViewController.m :包含内购核心逻辑的视图控制器
-
- 支持三个指定的内购商品:001Liu、002Liu和003Liu
- 实现了商品信息请求、购买流程、交易处理和恢复购买功能
- 使用了StoreKit.framework框架
-
- Main.storyboard :包含完整的内购UI界面,有商品展示和购买按钮
- LaunchScreen.storyboard :启动界面
-
- Info.plist :设置了Bundle Identifier为Liu.GameTest
- project.pbxproj :完整的Xcode项目配置,包含正确的文件路径引用
-
- 遵循Xcode标准目录结构,所有源文件位于 iOSInAppPurchaseDemo 目录下
- storyboard文件位于 iOSInAppPurchaseDemo/Base.lproj 目录下
- 创建了 .gitignore 文件忽略不需要版本控制的文件
objectivec
//
// ViewController.m
// iOSInAppPurchaseDemo
//
// Created by 卓伊凡 on 2025/9/12.
//
#import "ViewController.h"
@interface ViewController () {
NSArray *_productIdentifiers;
NSArray *_products;
SKProductsRequest *_productsRequest;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 设置商品ID数组
_productIdentifiers = @[@"001Liu", @"002Liu", @"003Liu"];
// 注册为交易观察者
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
// 初始化状态标签
self.statusLabel.text = @"准备加载商品信息...";
// 请求商品信息
[self requestProductsWithIdentifiers:_productIdentifiers];
}
- (void)dealloc {
// 移除交易观察者
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
#pragma mark - 请求商品信息
- (void)requestProductsWithIdentifiers:(NSArray *)productIdentifiers {
if ([SKPaymentQueue canMakePayments]) {
NSSet *productIdentifierSet = [NSSet setWithArray:productIdentifiers];
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifierSet];
_productsRequest.delegate = self;
[_productsRequest start];
} else {
self.statusLabel.text = @"当前设备不支持应用内购买";
}
}
#pragma mark - SKProductsRequestDelegate 代理方法
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
// 存储有效商品
_products = response.products;
// 打印无效的商品ID
if (response.invalidProductIdentifiers.count > 0) {
NSLog(@"无效的商品ID: %@", response.invalidProductIdentifiers);
}
// 更新UI,显示商品信息
[self updateProductUI];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"请求商品信息失败: %@", error.localizedDescription);
self.statusLabel.text = [NSString stringWithFormat:@"加载商品信息失败: %@", error.localizedDescription];
}
- (void)requestDidFinish:(SKRequest *)request {
// 请求完成
}
#pragma mark - 更新商品UI
- (void)updateProductUI {
if (_products.count == 0) {
self.statusLabel.text = @"未找到商品信息,请检查商品ID是否正确";
return;
}
self.statusLabel.text = @"商品信息加载完成,点击购买按钮进行购买";
// 根据商品ID匹配对应的商品信息
for (SKProduct *product in _products) {
NSString *productID = product.productIdentifier;
NSString *productTitle = product.localizedTitle;
NSString *productDescription = product.localizedDescription;
NSString *priceString = [self priceStringForProduct:product];
NSString *displayText = [NSString stringWithFormat:@"%@\n%@\n价格: %@", productTitle, productDescription, priceString];
if ([productID isEqualToString:@"001Liu"]) {
self.product1Label.text = displayText;
} else if ([productID isEqualToString:@"002Liu"]) {
self.product2Label.text = displayText;
} else if ([productID isEqualToString:@"003Liu"]) {
self.product3Label.text = displayText;
}
}
}
#pragma mark - 格式化价格
- (NSString *)priceStringForProduct:(SKProduct *)product {
NSNumberFormatter *priceFormatter = [[NSNumberFormatter alloc] init];
[priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[priceFormatter setLocale:product.priceLocale];
return [priceFormatter stringFromNumber:product.price];
}
#pragma mark - 购买按钮点击事件
- (IBAction)purchaseProduct1:(id)sender {
[self purchaseProductWithIdentifier:@"001Liu"];
}
- (IBAction)purchaseProduct2:(id)sender {
[self purchaseProductWithIdentifier:@"002Liu"];
}
- (IBAction)purchaseProduct3:(id)sender {
[self purchaseProductWithIdentifier:@"003Liu"];
}
#pragma mark - 发起购买请求
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier {
if (![SKPaymentQueue canMakePayments]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"购买失败" message:@"当前设备不支持应用内购买" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return;
}
// 查找对应的商品
SKProduct *productToPurchase = nil;
for (SKProduct *product in _products) {
if ([product.productIdentifier isEqualToString:productIdentifier]) {
productToPurchase = product;
break;
}
}
if (productToPurchase) {
self.statusLabel.text = [NSString stringWithFormat:@"正在发起购买请求: %@", productToPurchase.localizedTitle];
// 创建支付请求
SKPayment *payment = [SKPayment paymentWithProduct:productToPurchase];
// 将支付请求添加到支付队列
[[SKPaymentQueue defaultQueue] addPayment:payment];
} else {
self.statusLabel.text = @"未找到该商品信息";
}
}
#pragma mark - SKPaymentTransactionObserver 代理方法
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
// 购买中
self.statusLabel.text = @"购买处理中...";
break;
case SKPaymentTransactionStatePurchased:
// 购买成功
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
// 购买失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
// 恢复购买
[self restoreTransaction:transaction];
break;
case SKPaymentTransactionStateDeferred:
// 购买延迟(儿童模式需要家长批准)
self.statusLabel.text = @"购买等待批准...";
break;
default:
break;
}
}
}
#pragma mark - 处理交易结果
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
// 获取购买的商品ID
NSString *productID = transaction.payment.productIdentifier;
// 这里可以添加解锁内容、保存购买记录等逻辑
// 显示购买成功提示
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"购买成功" message:[NSString stringWithFormat:@"您已成功购买: %@", productID] preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
self.statusLabel.text = [NSString stringWithFormat:@"购买成功: %@", productID];
// 完成交易,从队列中移除
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
// 处理购买失败
if (transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"购买失败: %@", transaction.error.localizedDescription);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"购买失败" message:transaction.error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
self.statusLabel.text = [NSString stringWithFormat:@"购买失败: %@", transaction.error.localizedDescription];
} else {
// 用户取消购买
self.statusLabel.text = @"购买已取消";
}
// 完成交易,从队列中移除
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
// 获取恢复的商品ID
NSString *productID = transaction.originalTransaction.payment.productIdentifier;
// 这里可以添加解锁内容等逻辑
// 显示恢复购买成功提示
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"恢复购买成功" message:[NSString stringWithFormat:@"您已成功恢复购买: %@", productID] preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
self.statusLabel.text = [NSString stringWithFormat:@"恢复购买成功: %@", productID];
// 完成交易,从队列中移除
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
#pragma mark - 恢复购买
- (IBAction)restorePurchases:(id)sender {
if ([SKPaymentQueue canMakePayments]) {
self.statusLabel.text = @"正在恢复购买...";
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
} else {
self.statusLabel.text = @"当前设备不支持应用内购买";
}
}
@end

xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="iOSInAppPurchaseDemo" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<!-- 标题标签 -->
<label userLabel="标题" text="应用内购买演示" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-1X-jkq">
<rect key="frame" x="37.5" y="44" width="300" height="40"/>
<fontDescription key="fontDescription" type="system" pointSize="24" weight="medium"/>
<color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<!-- 状态标签 -->
<label userLabel="状态" text="加载中..." textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e2r-2Y-lkq">
<rect key="frame" x="37.5" y="94" width="300" height="40"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.5764705882" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<!-- 商品1 -->
<view userLabel="商品1容器" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d1v-3X-mkq">
<rect key="frame" x="37.5" y="154" width="300" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label userLabel="商品1信息" text="商品信息加载中..." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f1v-4X-nkq">
<rect key="frame" x="0.0" y="0.0" width="220" height="100"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button userLabel="购买按钮1" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g1v-5X-okq">
<rect key="frame" x="230" y="30" width="60" height="40"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="14" weight="medium"/>
<state key="normal" title="购买"/>
<color key="backgroundColor" red="0.0" green="0.4784313725" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</button>
</subviews>
<color key="backgroundColor" red="0.9411764706" green="0.9411764706" blue="0.9411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="f1v-4X-nkq" firstAttribute="top" secondItem="d1v-3X-mkq" secondAttribute="top" constant="0.0" id="a1c-6X-pkq"/>
<constraint firstAttribute="bottom" secondItem="f1v-4X-nkq" secondAttribute="bottom" constant="0.0" id="b1c-7X-qkq"/>
<constraint firstItem="f1v-4X-nkq" firstAttribute="leading" secondItem="d1v-3X-mkq" secondAttribute="leading" constant="10" id="c1c-8X-rkq"/>
<constraint firstItem="g1v-5X-okq" firstAttribute="trailing" secondItem="d1v-3X-mkq" secondAttribute="trailing" constant="-10" id="d1c-9X-skq"/>
<constraint firstAttribute="bottom" secondItem="g1v-5X-okq" secondAttribute="bottom" constant="30" id="e1c-0Y-tkq"/>
<constraint firstItem="g1v-5X-okq" firstAttribute="top" secondItem="d1v-3X-mkq" secondAttribute="top" constant="30" id="f1c-1Y-ukq"/>
<constraint firstAttribute="trailing" secondItem="f1v-4X-nkq" secondAttribute="trailing" constant="70" id="g1c-2Y-vkq"/>
<constraint firstItem="g1v-5X-okq" firstAttribute="leading" secondItem="f1v-4X-nkq" secondAttribute="trailing" constant="10" id="h1c-3Y-wkq"/>
</constraints>
<userLabel value="商品1容器"/>
<cornerRadius key="cornerRadius" value="8"/>
</view>
<!-- 商品2 -->
<view userLabel="商品2容器" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="h1v-4X-xkq">
<rect key="frame" x="37.5" y="264" width="300" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label userLabel="商品2信息" text="商品信息加载中..." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i1v-5X-ykq">
<rect key="frame" x="0.0" y="0.0" width="220" height="100"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button userLabel="购买按钮2" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="j1v-6X-zkq">
<rect key="frame" x="230" y="30" width="60" height="40"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="14" weight="medium"/>
<state key="normal" title="购买"/>
<color key="backgroundColor" red="0.0" green="0.4784313725" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</button>
</subviews>
<color key="backgroundColor" red="0.9411764706" green="0.9411764706" blue="0.9411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="i1v-5X-ykq" firstAttribute="top" secondItem="h1v-4X-xkq" secondAttribute="top" constant="0.0" id="a2c-7X-0kq"/>
<constraint firstAttribute="bottom" secondItem="i1v-5X-ykq" secondAttribute="bottom" constant="0.0" id="b2c-8X-1kq"/>
<constraint firstItem="i1v-5X-ykq" firstAttribute="leading" secondItem="h1v-4X-xkq" secondAttribute="leading" constant="10" id="c2c-9X-2kq"/>
<constraint firstItem="j1v-6X-zkq" firstAttribute="trailing" secondItem="h1v-4X-xkq" secondAttribute="trailing" constant="-10" id="d2c-0Y-3kq"/>
<constraint firstAttribute="bottom" secondItem="j1v-6X-zkq" secondAttribute="bottom" constant="30" id="e2c-1Y-4kq"/>
<constraint firstItem="j1v-6X-zkq" firstAttribute="top" secondItem="h1v-4X-xkq" secondAttribute="top" constant="30" id="f2c-2Y-5kq"/>
<constraint firstAttribute="trailing" secondItem="i1v-5X-ykq" secondAttribute="trailing" constant="70" id="g2c-3Y-6kq"/>
<constraint firstItem="j1v-6X-zkq" firstAttribute="leading" secondItem="i1v-5X-ykq" secondAttribute="trailing" constant="10" id="h2c-4Y-7kq"/>
</constraints>
<userLabel value="商品2容器"/>
<cornerRadius key="cornerRadius" value="8"/>
</view>
<!-- 商品3 -->
<view userLabel="商品3容器" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="k1v-7X-8kq">
<rect key="frame" x="37.5" y="374" width="300" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label userLabel="商品3信息" text="商品信息加载中..." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l1v-8X-9kq">
<rect key="frame" x="0.0" y="0.0" width="220" height="100"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button userLabel="购买按钮3" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m1v-9X-akq">
<rect key="frame" x="230" y="30" width="60" height="40"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="14" weight="medium"/>
<state key="normal" title="购买"/>
<color key="backgroundColor" red="0.0" green="0.4784313725" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</button>
</subviews>
<color key="backgroundColor" red="0.9411764706" green="0.9411764706" blue="0.9411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="l1v-8X-9kq" firstAttribute="top" secondItem="k1v-7X-8kq" secondAttribute="top" constant="0.0" id="a3c-0Y-bkq"/>
<constraint firstAttribute="bottom" secondItem="l1v-8X-9kq" secondAttribute="bottom" constant="0.0" id="b3c-1Y-ckq"/>
<constraint firstItem="l1v-8X-9kq" firstAttribute="leading" secondItem="k1v-7X-8kq" secondAttribute="leading" constant="10" id="c3c-2Y-dkq"/>
<constraint firstItem="m1v-9X-akq" firstAttribute="trailing" secondItem="k1v-7X-8kq" secondAttribute="trailing" constant="-10" id="d3c-3Y-ekq"/>
<constraint firstAttribute="bottom" secondItem="m1v-9X-akq" secondAttribute="bottom" constant="30" id="e3c-4Y-fkq"/>
<constraint firstItem="m1v-9X-akq" firstAttribute="top" secondItem="k1v-7X-8kq" secondAttribute="top" constant="30" id="f3c-5Y-gkq"/>
<constraint firstAttribute="trailing" secondItem="l1v-8X-9kq" secondAttribute="trailing" constant="70" id="g3c-6Y-hkq"/>
<constraint firstItem="m1v-9X-akq" firstAttribute="leading" secondItem="l1v-8X-9kq" secondAttribute="trailing" constant="10" id="h3c-7Y-ikq"/>
</constraints>
<userLabel value="商品3容器"/>
<cornerRadius key="cornerRadius" value="8"/>
</view>
<!-- 恢复购买按钮 -->
<button userLabel="恢复购买" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="n1v-8X-jkq">
<rect key="frame" x="37.5" y="494" width="300" height="40"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="16" weight="medium"/>
<state key="normal" title="恢复购买"/>
<color key="backgroundColor" red="0.6862745098" green="0.6862745098" blue="0.6862745098" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<!-- 标题约束 -->
<constraint firstItem="e1v-1X-jkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="a4c-9X-kkq"/>
<constraint firstItem="e1v-1X-jkq" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="0.0" id="b4c-0Y-lkq"/>
<constraint firstAttribute="trailing" secondItem="e1v-1X-jkq" secondAttribute="trailing" constant="37.5" id="c4c-1Y-mkq"/>
<!-- 状态约束 -->
<constraint firstItem="e2r-2Y-lkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="d4c-2Y-nkq"/>
<constraint firstItem="e2r-2Y-lkq" firstAttribute="top" secondItem="e1v-1X-jkq" secondAttribute="bottom" constant="10" id="e4c-3Y-okq"/>
<constraint firstAttribute="trailing" secondItem="e2r-2Y-lkq" secondAttribute="trailing" constant="37.5" id="f4c-4Y-pkq"/>
<!-- 商品1约束 -->
<constraint firstItem="d1v-3X-mkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="g4c-5Y-qkq"/>
<constraint firstItem="d1v-3X-mkq" firstAttribute="top" secondItem="e2r-2Y-lkq" secondAttribute="bottom" constant="20" id="h4c-6Y-rkq"/>
<constraint firstAttribute="trailing" secondItem="d1v-3X-mkq" secondAttribute="trailing" constant="37.5" id="i4c-7Y-skq"/>
<constraint firstAttribute="width" secondItem="d1v-3X-mkq" secondAttribute="width" multiplier="1" constant="0.0" id="j4c-8Y-tkq"/>
<constraint firstItem="d1v-3X-mkq" firstAttribute="height" constant="100" id="k4c-9Y-ukq"/>
<!-- 商品2约束 -->
<constraint firstItem="h1v-4X-xkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="l4c-0Z-vkq"/>
<constraint firstItem="h1v-4X-xkq" firstAttribute="top" secondItem="d1v-3X-mkq" secondAttribute="bottom" constant="10" id="m4c-1Z-wkq"/>
<constraint firstAttribute="trailing" secondItem="h1v-4X-xkq" secondAttribute="trailing" constant="37.5" id="n4c-2Z-xkq"/>
<constraint firstItem="h1v-4X-xkq" firstAttribute="height" constant="100" id="o4c-3Z-ykq"/>
<!-- 商品3约束 -->
<constraint firstItem="k1v-7X-8kq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="p4c-4Z-zkq"/>
<constraint firstItem="k1v-7X-8kq" firstAttribute="top" secondItem="h1v-4X-xkq" secondAttribute="bottom" constant="10" id="q4c-5Z-0kq"/>
<constraint firstAttribute="trailing" secondItem="k1v-7X-8kq" secondAttribute="trailing" constant="37.5" id="r4c-6Z-1kq"/>
<constraint firstItem="k1v-7X-8kq" firstAttribute="height" constant="100" id="s4c-7Z-2kq"/>
<!-- 恢复购买按钮约束 -->
<constraint firstItem="n1v-8X-jkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="t4c-8Z-3kq"/>
<constraint firstItem="n1v-8X-jkq" firstAttribute="top" secondItem="k1v-7X-8kq" secondAttribute="bottom" constant="20" id="u4c-9Z-4kq"/>
<constraint firstAttribute="trailing" secondItem="n1v-8X-jkq" secondAttribute="trailing" constant="37.5" id="v4c-0a-5kq"/>
<constraint firstItem="n1v-8X-jkq" firstAttribute="height" constant="40" id="w4c-1a-6kq"/>
</constraints>
</view>
<connections>
<outlet property="product1Label" destination="f1v-4X-nkq" id="con-0a-7kq"/>
<outlet property="product2Label" destination="i1v-5X-ykq" id="con-1a-8kq"/>
<outlet property="product3Label" destination="l1v-8X-9kq" id="con-2a-9kq"/>
<outlet property="product1Button" destination="g1v-5X-okq" id="con-3a-akq"/>
<outlet property="product2Button" destination="j1v-6X-zkq" id="con-4a-bkq"/>
<outlet property="product3Button" destination="m1v-9X-akq" id="con-5a-ckq"/>
<outlet property="statusLabel" destination="e2r-2Y-lkq" id="con-6a-dkq"/>
<action selector="purchaseProduct1:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-7a-ekq">
<sender slot="source" id="g1v-5X-okq"/>
</action>
<action selector="purchaseProduct2:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-8a-fkq">
<sender slot="source" id="j1v-6X-zkq"/>
</action>
<action selector="purchaseProduct3:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-9a-gkq">
<sender slot="source" id="m1v-9X-akq"/>
</action>
<action selector="restorePurchases:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-0b-hkq">
<sender slot="source" id="n1v-8X-jkq"/>
</action>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFilesOwner" id="dkx-z0-nzr" sceneMemberID="filesOwner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="26j-XQyn0c" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="137.5" y="102.5"/>
</scene>
</scenes>
</document>
建立info.list创建Info.plist文件,配置应用程序的基本信息,包括应用名称、版本、Bundle Identifier等。
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>iOSInAppPurchaseDemo</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>Liu.GameTest</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
创建LaunchScreen.storyboard文件,作为应用程序的启动界面
xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" launchScreen="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label userLabel="启动标题" text="iOS内购演示" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-1X-jkq">
<rect key="frame" x="75" y="276.5" width="225" height="40"/>
<fontDescription key="fontDescription" type="system" pointSize="30" weight="medium"/>
<color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label userLabel="启动副标题" text="Loading..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e2v-2X-lkq">
<rect key="frame" x="75" y="326.5" width="225" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.5764705882" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="e1v-1X-jkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="75" id="a1c-3X-mkq"/>
<constraint firstItem="e1v-1X-jkq" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" constant="-25" id="b1c-4X-nkq"/>
<constraint firstAttribute="trailing" secondItem="e1v-1X-jkq" secondAttribute="trailing" constant="75" id="c1c-5X-okq"/>
<constraint firstItem="e2v-2X-lkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="75" id="d1c-6X-pkq"/>
<constraint firstItem="e2v-2X-lkq" firstAttribute="top" secondItem="e1v-1X-jkq" secondAttribute="bottom" constant="10" id="e1c-7X-qkq"/>
<constraint firstAttribute="trailing" secondItem="e2v-2X-lkq" secondAttribute="trailing" constant="75" id="f1c-8X-rkq"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFilesOwner" id="dkx-z0-nzr" sceneMemberID="filesOwner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="26j-XQyn0c" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="137.5" y="102.5"/>
</scene>
</scenes>
</document>
aunchScreen.storyboard已创建完成。现在需要创建Xcode项目配置文件project.pbxproj,这是项目的核心配置文件,定义了项目结构和构建设置。
ini
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
19786D9D1E8E4F5A00D94E8C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 19786D9C1E8E4F5A00D94E8C /* AppDelegate.m */; };
19786D9F1E8E4F5A00D94E8C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 19786D9E1E8E4F5A00D94E8C /* ViewController.m */; };
19786DA31E8E4F5A00D94E8C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19786DA21E8E4F5A00D94E8C /* Main.storyboard */; };
19786DA51E8E4F5A00D94E8C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19786DA41E8E4F5A00D94E8C /* LaunchScreen.storyboard */; };
19786DA71E8E4F5A00D94E8C /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 19786DA61E8E4F5A00D94E8C /* Info.plist */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
19786D9A1E8E4F5A00D94E8C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iOSInAppPurchaseDemo/AppDelegate.h; sourceTree = "<group>"; };
19786D9C1E8E4F5A00D94E8C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSInAppPurchaseDemo/AppDelegate.m; sourceTree = "<group>"; };
19786D9D1E8E4F5A00D94E8C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iOSInAppPurchaseDemo/ViewController.h; sourceTree = "<group>"; };
19786D9E1E8E4F5A00D94E8C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSInAppPurchaseDemo/ViewController.m; sourceTree = "<group>"; };
19786DA21E8E4F5A00D94E8C /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = iOSInAppPurchaseDemo/Base.lproj/Main.storyboard; sourceTree = "<group>"; };
19786DA41E8E4F5A00D94E8C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = iOSInAppPurchaseDemo/Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
19786DA61E8E4F5A00D94E8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = iOSInAppPurchaseDemo/Info.plist; sourceTree = "<group>"; };
19786DA91E8E4F5A00D94E8C /* iOSInAppPurchaseDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSInAppPurchaseDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
19786DAA1E8E4F5A00D94E8C /* SubscriptionKey_Q9W29CG749.p8 */ = {isa = PBXFileReference; lastKnownFileType = text; path = SubscriptionKey_Q9W29CG749.p8; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
19786D931E8E4F5A00D94E8C = {isa = PBXGroup; children = (
19786D9A1E8E4F5A00D94E8C /* AppDelegate.h */,
19786D9C1E8E4F5A00D94E8C /* AppDelegate.m */,
19786D9D1E8E4F5A00D94E8C /* ViewController.h */,
19786D9E1E8E4F5A00D94E8C /* ViewController.m */,
19786DA01E8E4F5A00D94E8C /* Base.lproj */,
19786DA61E8E4F5A00D94E8C /* Info.plist */,
); path = iOSInAppPurchaseDemo; sourceTree = "<group>"; };
19786DA01E8E4F5A00D94E8C /* Base.lproj */ = {isa = PBXGroup; children = (
19786DA21E8E4F5A00D94E8C /* Main.storyboard */,
19786DA41E8E4F5A00D94E8C /* LaunchScreen.storyboard */,
); path = Base.lproj; sourceTree = "<group>"; };
19786D921E8E4F5A00D94E8C = {isa = PBXGroup; children = (
19786D931E8E4F5A00D94E8C /* iOSInAppPurchaseDemo */,
19786DA91E8E4F5A00D94E8C /* Products */,
); sourceTree = "<group>"; };
19786DA91E8E4F5A00D94E8C = {isa = PBXGroup; children = (
19786DA91E8E4F5A00D94E8C /* iOSInAppPurchaseDemo.app */,
); name = Products; sourceTree = "<group>"; };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
19786D991E8E4F5A00D94E8C = {isa = PBXNativeTarget; buildConfigurationList = 19786DAE1E8E4F5A00D94E8C /* Build configuration list for PBXNativeTarget "iOSInAppPurchaseDemo" */; buildPhases = (
19786D951E8E4F5A00D94E8C /* Sources */,
19786D961E8E4F5A00D94E8C /* Resources */,
19786D971E8E4F5A00D94E8C /* Frameworks */,
); buildRules = (
); dependencies = (
); name = iOSInAppPurchaseDemo; productName = iOSInAppPurchaseDemo; productReference = 19786DA91E8E4F5A00D94E8C /* iOSInAppPurchaseDemo.app */; productType = "com.apple.product-type.application"; };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
19786D911E8E4F5A00D94E8C = {isa = PBXProject; attributes = {
LastSwiftUpdateCheck = 0900;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = "Your Organization";
targetAttributes = {
19786D991E8E4F5A00D94E8C = {
CreatedOnToolsVersion = 9.0;
LastSwiftMigration = 0900;
};
};
};
buildConfigurationList = 19786D901E8E4F5A00D94E8C /* Build configuration list for PBXProject "iOSInAppPurchaseDemo" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = "zh_CN";
hasScannedForEncodings = 0;
mainGroup = 19786D921E8E4F5A00D94E8C;
productRefGroup = 19786DA91E8E4F5A00D94E8C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
19786D991E8E4F5A00D94E8C /* iOSInAppPurchaseDemo */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
19786D961E8E4F5A00D94E8C /* Resources */ = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647;
files = (
19786DA31E8E4F5A00D94E8C /* Main.storyboard in Resources */,
19786DA51E8E4F5A00D94E8C /* LaunchScreen.storyboard in Resources */,
19786DA71E8E4F5A00D94E8C /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
19786D951E8E4F5A00D94E8C /* Sources */ = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647;
files = (
19786D9D1E8E4F5A00D94E8C /* AppDelegate.m in Sources */,
19786D9F1E8E4F5A00D94E8C /* ViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXFrameworksBuildPhase section */
19786D971E8E4F5A00D94E8C /* Frameworks */ = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647;
files = (
19786DB01E8E4F5A00D94E8C /* StoreKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXBuildFile for Frameworks */
19786DB01E8E4F5A00D94E8C /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 19786DB11E8E4F5A00D94E8C /* StoreKit.framework */; };
/* End PBXBuildFile for Frameworks */
/* Begin PBXFileReference for Frameworks */
19786DB11E8E4F5A00D94E8C /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
/* End PBXFileReference for Frameworks */
/* Begin PBXBuildConfiguration section */
19786D9B1E8E4F5A00D94E8C /* Debug */ = {isa = PBXBuildConfiguration; buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
19786D9C1E8E4F5A00D94E8C /* Release */ = {isa = PBXBuildConfiguration; buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
19786DAF1E8E4F5A00D94E8C /* Debug */ = {isa = PBXBuildConfiguration; buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
INFOPLIST_FILE = "iOSInAppPurchaseDemo/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_BUNDLE_IDENTIFIER = Liu.GameTest;
PRODUCT_NAME = iOSInAppPurchaseDemo;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
19786DB01E8E4F5A00D94E8C /* Release */ = {isa = PBXBuildConfiguration; buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
INFOPLIST_FILE = "iOSInAppPurchaseDemo/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_BUNDLE_IDENTIFIER = Liu.GameTest;
PRODUCT_NAME = iOSInAppPurchaseDemo;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End PBXBuildConfiguration section */
/* Begin XCConfigurationList section */
19786D901E8E4F5A00D94E8C /* Build configuration list for PBXProject "iOSInAppPurchaseDemo" */ = {isa = XCConfigurationList; buildConfigurations = (
19786D9B1E8E4F5A00D94E8C /* Debug */,
19786D9C1E8E4F5A00D94E8C /* Release */,
); defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
19786DAE1E8E4F5A00D94E8C /* Build configuration list for PBXNativeTarget "iOSInAppPurchaseDemo" */ = {isa = XCConfigurationList; buildConfigurations = (
19786DAF1E8E4F5A00D94E8C /* Debug */,
19786DB01E8E4F5A00D94E8C /* Release */,
); defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 19786D911E8E4F5A00D94E8C;
}
核心内容参考已经 写完,更多内容可以参考 开源出来的 文件

整个demo实例:
iOS 内购开发 (In-App Purchase) Objective-C Storyboard 实例
项目介绍
这是一个基于 Objective-C 和 Storyboard 的 iOS 内购开发示例项目,展示了如何在 iOS 应用中实现苹果应用内购买功能。
功能特性
- 支持多个内购商品的展示和购买
- 实现商品信息请求与展示
- 完整的购买流程处理
- 恢复已购买商品功能
- 错误处理和状态反馈
技术架构
- 开发语言: Objective-C
- 界面设计: Storyboard
- 内购框架: StoreKit.framework
- 最低支持系统: iOS 12.0
- Bundle Identifier: Liu.GameTest
项目结构
bash
iOSInAppPurchaseDemo/
├── AppDelegate.h # 应用程序委托头文件
├── AppDelegate.m # 应用程序委托实现
├── ViewController.h # 主视图控制器头文件
├── ViewController.m # 主视图控制器实现(包含内购核心逻辑)
├── Info.plist # 应用配置文件
└── Base.lproj/
├── Main.storyboard # 主界面故事板
└── LaunchScreen.storyboard # 启动界面故事板
内购商品配置
项目中配置了三个测试商品ID:
- 001Liu
- 002Liu
- 003Liu
安装与运行
- 克隆或下载本项目
- 打开
iOSInAppPurchaseDemo.xcodeproj
文件 - 确保已在 Xcode 中配置开发者账号
- 在真机或模拟器上运行项目
注意事项
- 内购功能需要在真机上进行完整测试
- 在 App Store Connect 中配置相应的内购项目才能进行真实购买
- 项目包含测试用的 SubscriptionKey_Q9W29CG749.p8 文件
- 实际项目中请替换为自己的密钥文件和 Bundle Identifier
使用说明
- 启动应用后,系统会自动请求商品信息
- 点击对应商品的购买按钮进行购买
- 输入 Apple ID 密码完成支付
- 购买成功后会显示成功提示
- 可以点击"恢复购买"按钮恢复已购买的商品
开发提示
- 确保在 Info.plist 中正确配置了应用的 Bundle Identifier
- 在 App Store Connect 中创建相应的应用和内购项目
- 测试内购时使用沙盒测试账号
- 处理各种交易状态和错误情况
License
© 2025 优雅草科技. All rights reserved.