完整的苹果内购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设计,包括商品信息展示和购买按钮。 创建的结构目录要符合常规项目需要,最后我会放出结构。 1.
创建了核心源文件: AppDelegate.h 和 AppDelegate.m :应用程序委托类 - ViewController.h 和 ViewController.m :包含内购核心逻辑的视图控制器
2.
实现了内购功能: 支持三个指定的内购商品:001Liu、002Liu和003Liu - 实现了商品信息请求、购买流程、交易处理和恢复购买功能
- 使用了StoreKit.framework框架
3.
创建了界面文件: Main.storyboard :包含完整的内购UI界面,有商品展示和购买按钮 - LaunchScreen.storyboard :启动界面
4.
配置文件: Info.plist :设置了Bundle Identifier为Liu.GameTest - project.pbxproj :完整的Xcode项目配置,包含正确的文件路径引用
5.
项目结构: 遵循Xcode标准目录结构,所有源文件位于 iOSInAppPurchaseDemo 目录下 - storyboard文件位于 iOSInAppPurchaseDemo/Base.lproj 目录下
- 创建了 .gitignore 文件忽略不需要版本控制的文件
//// 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 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 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 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,这是项目的核心配置文件,定义了项目结构和构建设置。 // !$*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
项目结构iOSInAppPurchaseDemo/├── AppDelegate.h # 应用程序委托头文件├── AppDelegate.m # 应用程序委托实现├── ViewController.h # 主视图控制器头文件├── ViewController.m # 主视图控制器实现(包含内购核心逻辑)├── Info.plist # 应用配置文件└── Base.lproj/ ├── Main.storyboard # 主界面故事板 └── LaunchScreen.storyboard # 启动界面故事板内购商品配置项目中配置了三个测试商品ID: 安装与运行- 克隆或下载本项目
- 打开 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.
|