0 评论

0 收藏

分享

[object-c开发相关技术] 完整的苹果内购in-purchase实例-采用object-c语言使用苹果的Storyboard开发模式的完整

完整的苹果内购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:
  • 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.

优雅草论坛2022年8月11日大改,优雅草论坛变回只服务于客户的提问交流论坛,详情查看优雅草8月11日大改,原因详情查优雅草外卖乐关闭

回复

举报 使用道具

全部回复
暂无回帖,快来参与回复吧
yac2025
优雅草的临时工
主题 328
回复 0
粉丝 0