完整的苹果内购in-purchase实例-采用object-c语言使用苹果的Storyboard开发模式的完整购买实例

完整的苹果内购in-purchase实例-采用object-c语言使用苹果的Storyboard开发模式的完整购买实例

引言

此前 写了 object-c的实例,但是交付时候发现客户采用的是Storyboard开发模式,因此我们只能单独再写Storyboard开发模式的代码。

项目开源地址:

gitee.com/youyacao/io...

内容如下

首先单独去建立了内购的密钥

密钥的ID为Q9W29CG749 对应的 app 包名 Liu.GameTest

密钥放到本地

对应的内购商品

产品参考名称: LiuAppGame1Item1 产品id: 001Liu

产品参考名称: LiuAppGame1Item2 产品id: 002Liu

产品参考名称: LiuAppGame1Item3 产品id: 003Liu

3个商品对应三种金额,对应不同的价格。

已创建AppDelegate.h文件,现在创建AppDelegate.m文件来实现应用程序的生命周期管理。

创建ViewController.m文件,实现内购的核心逻辑,包括加载商品信息、处理购买请求和交易结果等功能。

创建Main.storyboard文件,实现内购界面的UI设计,包括商品信息展示和购买按钮。

创建的结构目录要符合常规项目需要,最后我会放出结构。

    创建了核心源文件:
  • AppDelegate.h 和 AppDelegate.m :应用程序委托类
  • ViewController.h 和 ViewController.m :包含内购核心逻辑的视图控制器
    实现了内购功能:
  • 支持三个指定的内购商品:001Liu、002Liu和003Liu
  • 实现了商品信息请求、购买流程、交易处理和恢复购买功能
  • 使用了StoreKit.framework框架
    创建了界面文件:
  • Main.storyboard :包含完整的内购UI界面,有商品展示和购买按钮
  • LaunchScreen.storyboard :启动界面
    配置文件:
  • Info.plist :设置了Bundle Identifier为Liu.GameTest
  • project.pbxproj :完整的Xcode项目配置,包含正确的文件路径引用
    项目结构:
  • 遵循Xcode标准目录结构,所有源文件位于 iOSInAppPurchaseDemo 目录下
  • storyboard文件位于 iOSInAppPurchaseDemo/Base.lproj 目录下
  • 创建了 .gitignore 文件忽略不需要版本控制的文件
objectivec 复制代码
//
//  ViewController.m
//  iOSInAppPurchaseDemo
//
//  Created by 卓伊凡 on 2025/9/12.
//

#import "ViewController.h"

@interface ViewController () {
    NSArray *_productIdentifiers;
    NSArray *_products;
    SKProductsRequest *_productsRequest;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 设置商品ID数组
    _productIdentifiers = @[@"001Liu", @"002Liu", @"003Liu"];

    // 注册为交易观察者
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    // 初始化状态标签
    self.statusLabel.text = @"准备加载商品信息...";

    // 请求商品信息
    [self requestProductsWithIdentifiers:_productIdentifiers];
}

- (void)dealloc {
    // 移除交易观察者
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

#pragma mark - 请求商品信息

- (void)requestProductsWithIdentifiers:(NSArray *)productIdentifiers {
    if ([SKPaymentQueue canMakePayments]) {
        NSSet *productIdentifierSet = [NSSet setWithArray:productIdentifiers];
        _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifierSet];
        _productsRequest.delegate = self;
        [_productsRequest start];
    } else {
        self.statusLabel.text = @"当前设备不支持应用内购买";
    }
}

#pragma mark - SKProductsRequestDelegate 代理方法

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    // 存储有效商品
    _products = response.products;

    // 打印无效的商品ID
    if (response.invalidProductIdentifiers.count > 0) {
        NSLog(@"无效的商品ID: %@", response.invalidProductIdentifiers);
    }

    // 更新UI,显示商品信息
    [self updateProductUI];
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    NSLog(@"请求商品信息失败: %@", error.localizedDescription);
    self.statusLabel.text = [NSString stringWithFormat:@"加载商品信息失败: %@", error.localizedDescription];
}

- (void)requestDidFinish:(SKRequest *)request {
    // 请求完成
}

#pragma mark - 更新商品UI

- (void)updateProductUI {
    if (_products.count == 0) {
        self.statusLabel.text = @"未找到商品信息,请检查商品ID是否正确";
        return;
    }

    self.statusLabel.text = @"商品信息加载完成,点击购买按钮进行购买";

    // 根据商品ID匹配对应的商品信息
    for (SKProduct *product in _products) {
        NSString *productID = product.productIdentifier;
        NSString *productTitle = product.localizedTitle;
        NSString *productDescription = product.localizedDescription;
        NSString *priceString = [self priceStringForProduct:product];

        NSString *displayText = [NSString stringWithFormat:@"%@\n%@\n价格: %@", productTitle, productDescription, priceString];

        if ([productID isEqualToString:@"001Liu"]) {
            self.product1Label.text = displayText;
        } else if ([productID isEqualToString:@"002Liu"]) {
            self.product2Label.text = displayText;
        } else if ([productID isEqualToString:@"003Liu"]) {
            self.product3Label.text = displayText;
        }
    }
}

#pragma mark - 格式化价格

- (NSString *)priceStringForProduct:(SKProduct *)product {
    NSNumberFormatter *priceFormatter = [[NSNumberFormatter alloc] init];
    [priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    [priceFormatter setLocale:product.priceLocale];

    return [priceFormatter stringFromNumber:product.price];
}

#pragma mark - 购买按钮点击事件

- (IBAction)purchaseProduct1:(id)sender {
    [self purchaseProductWithIdentifier:@"001Liu"];
}

- (IBAction)purchaseProduct2:(id)sender {
    [self purchaseProductWithIdentifier:@"002Liu"];
}

- (IBAction)purchaseProduct3:(id)sender {
    [self purchaseProductWithIdentifier:@"003Liu"];
}

#pragma mark - 发起购买请求

- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier {
    if (![SKPaymentQueue canMakePayments]) {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"购买失败" message:@"当前设备不支持应用内购买" preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
        [self presentViewController:alert animated:YES completion:nil];
        return;
    }

    // 查找对应的商品
    SKProduct *productToPurchase = nil;
    for (SKProduct *product in _products) {
        if ([product.productIdentifier isEqualToString:productIdentifier]) {
            productToPurchase = product;
            break;
        }
    }

    if (productToPurchase) {
        self.statusLabel.text = [NSString stringWithFormat:@"正在发起购买请求: %@", productToPurchase.localizedTitle];

        // 创建支付请求
        SKPayment *payment = [SKPayment paymentWithProduct:productToPurchase];
        // 将支付请求添加到支付队列
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    } else {
        self.statusLabel.text = @"未找到该商品信息";
    }
}

#pragma mark - SKPaymentTransactionObserver 代理方法

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                // 购买中
                self.statusLabel.text = @"购买处理中...";
                break;
            case SKPaymentTransactionStatePurchased:
                // 购买成功
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                // 购买失败
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                // 恢复购买
                [self restoreTransaction:transaction];
                break;
            case SKPaymentTransactionStateDeferred:
                // 购买延迟(儿童模式需要家长批准)
                self.statusLabel.text = @"购买等待批准...";
                break;
            default:
                break;
        }
    }
}

#pragma mark - 处理交易结果

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    // 获取购买的商品ID
    NSString *productID = transaction.payment.productIdentifier;

    // 这里可以添加解锁内容、保存购买记录等逻辑

    // 显示购买成功提示
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"购买成功" message:[NSString stringWithFormat:@"您已成功购买: %@", productID] preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];

    self.statusLabel.text = [NSString stringWithFormat:@"购买成功: %@", productID];

    // 完成交易,从队列中移除
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    // 处理购买失败
    if (transaction.error.code != SKErrorPaymentCancelled) {
        NSLog(@"购买失败: %@", transaction.error.localizedDescription);

        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"购买失败" message:transaction.error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
        [self presentViewController:alert animated:YES completion:nil];

        self.statusLabel.text = [NSString stringWithFormat:@"购买失败: %@", transaction.error.localizedDescription];
    } else {
        // 用户取消购买
        self.statusLabel.text = @"购买已取消";
    }

    // 完成交易,从队列中移除
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    // 获取恢复的商品ID
    NSString *productID = transaction.originalTransaction.payment.productIdentifier;

    // 这里可以添加解锁内容等逻辑

    // 显示恢复购买成功提示
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"恢复购买成功" message:[NSString stringWithFormat:@"您已成功恢复购买: %@", productID] preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];

    self.statusLabel.text = [NSString stringWithFormat:@"恢复购买成功: %@", productID];

    // 完成交易,从队列中移除
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

#pragma mark - 恢复购买

- (IBAction)restorePurchases:(id)sender {
    if ([SKPaymentQueue canMakePayments]) {
        self.statusLabel.text = @"正在恢复购买...";
        [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    } else {
        self.statusLabel.text = @"当前设备不支持应用内购买";
    }
}

@end
xml 复制代码
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21504"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="iOSInAppPurchaseDemo" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <!-- 标题标签 -->
                            <label userLabel="标题" text="应用内购买演示" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-1X-jkq">
                                <rect key="frame" x="37.5" y="44" width="300" height="40"/>
                                <fontDescription key="fontDescription" type="system" pointSize="24" weight="medium"/>
                                <color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>

                            <!-- 状态标签 -->
                            <label userLabel="状态" text="加载中..." textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e2r-2Y-lkq">
                                <rect key="frame" x="37.5" y="94" width="300" height="40"/>
                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                <color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.5764705882" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>

                            <!-- 商品1 -->
                            <view userLabel="商品1容器" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d1v-3X-mkq">
                                <rect key="frame" x="37.5" y="154" width="300" height="100"/>
                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                <subviews>
                                    <label userLabel="商品1信息" text="商品信息加载中..." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f1v-4X-nkq">
                                        <rect key="frame" x="0.0" y="0.0" width="220" height="100"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                        <color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <nil key="highlightedColor"/>
                                    </label>
                                    <button userLabel="购买按钮1" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g1v-5X-okq">
                                        <rect key="frame" x="230" y="30" width="60" height="40"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14" weight="medium"/>
                                        <state key="normal" title="购买"/>
                                        <color key="backgroundColor" red="0.0" green="0.4784313725" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                    </button>
                                </subviews>
                                <color key="backgroundColor" red="0.9411764706" green="0.9411764706" blue="0.9411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <constraints>
                                    <constraint firstItem="f1v-4X-nkq" firstAttribute="top" secondItem="d1v-3X-mkq" secondAttribute="top" constant="0.0" id="a1c-6X-pkq"/>
                                    <constraint firstAttribute="bottom" secondItem="f1v-4X-nkq" secondAttribute="bottom" constant="0.0" id="b1c-7X-qkq"/>
                                    <constraint firstItem="f1v-4X-nkq" firstAttribute="leading" secondItem="d1v-3X-mkq" secondAttribute="leading" constant="10" id="c1c-8X-rkq"/>
                                    <constraint firstItem="g1v-5X-okq" firstAttribute="trailing" secondItem="d1v-3X-mkq" secondAttribute="trailing" constant="-10" id="d1c-9X-skq"/>
                                    <constraint firstAttribute="bottom" secondItem="g1v-5X-okq" secondAttribute="bottom" constant="30" id="e1c-0Y-tkq"/>
                                    <constraint firstItem="g1v-5X-okq" firstAttribute="top" secondItem="d1v-3X-mkq" secondAttribute="top" constant="30" id="f1c-1Y-ukq"/>
                                    <constraint firstAttribute="trailing" secondItem="f1v-4X-nkq" secondAttribute="trailing" constant="70" id="g1c-2Y-vkq"/>
                                    <constraint firstItem="g1v-5X-okq" firstAttribute="leading" secondItem="f1v-4X-nkq" secondAttribute="trailing" constant="10" id="h1c-3Y-wkq"/>
                                </constraints>
                                <userLabel value="商品1容器"/>
                                <cornerRadius key="cornerRadius" value="8"/>
                            </view>

                            <!-- 商品2 -->
                            <view userLabel="商品2容器" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="h1v-4X-xkq">
                                <rect key="frame" x="37.5" y="264" width="300" height="100"/>
                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                <subviews>
                                    <label userLabel="商品2信息" text="商品信息加载中..." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i1v-5X-ykq">
                                        <rect key="frame" x="0.0" y="0.0" width="220" height="100"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                        <color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <nil key="highlightedColor"/>
                                    </label>
                                    <button userLabel="购买按钮2" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="j1v-6X-zkq">
                                        <rect key="frame" x="230" y="30" width="60" height="40"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14" weight="medium"/>
                                        <state key="normal" title="购买"/>
                                        <color key="backgroundColor" red="0.0" green="0.4784313725" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                    </button>
                                </subviews>
                                <color key="backgroundColor" red="0.9411764706" green="0.9411764706" blue="0.9411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <constraints>
                                    <constraint firstItem="i1v-5X-ykq" firstAttribute="top" secondItem="h1v-4X-xkq" secondAttribute="top" constant="0.0" id="a2c-7X-0kq"/>
                                    <constraint firstAttribute="bottom" secondItem="i1v-5X-ykq" secondAttribute="bottom" constant="0.0" id="b2c-8X-1kq"/>
                                    <constraint firstItem="i1v-5X-ykq" firstAttribute="leading" secondItem="h1v-4X-xkq" secondAttribute="leading" constant="10" id="c2c-9X-2kq"/>
                                    <constraint firstItem="j1v-6X-zkq" firstAttribute="trailing" secondItem="h1v-4X-xkq" secondAttribute="trailing" constant="-10" id="d2c-0Y-3kq"/>
                                    <constraint firstAttribute="bottom" secondItem="j1v-6X-zkq" secondAttribute="bottom" constant="30" id="e2c-1Y-4kq"/>
                                    <constraint firstItem="j1v-6X-zkq" firstAttribute="top" secondItem="h1v-4X-xkq" secondAttribute="top" constant="30" id="f2c-2Y-5kq"/>
                                    <constraint firstAttribute="trailing" secondItem="i1v-5X-ykq" secondAttribute="trailing" constant="70" id="g2c-3Y-6kq"/>
                                    <constraint firstItem="j1v-6X-zkq" firstAttribute="leading" secondItem="i1v-5X-ykq" secondAttribute="trailing" constant="10" id="h2c-4Y-7kq"/>
                                </constraints>
                                <userLabel value="商品2容器"/>
                                <cornerRadius key="cornerRadius" value="8"/>
                            </view>

                            <!-- 商品3 -->
                            <view userLabel="商品3容器" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="k1v-7X-8kq">
                                <rect key="frame" x="37.5" y="374" width="300" height="100"/>
                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                <subviews>
                                    <label userLabel="商品3信息" text="商品信息加载中..." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l1v-8X-9kq">
                                        <rect key="frame" x="0.0" y="0.0" width="220" height="100"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                        <color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <nil key="highlightedColor"/>
                                    </label>
                                    <button userLabel="购买按钮3" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m1v-9X-akq">
                                        <rect key="frame" x="230" y="30" width="60" height="40"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14" weight="medium"/>
                                        <state key="normal" title="购买"/>
                                        <color key="backgroundColor" red="0.0" green="0.4784313725" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                    </button>
                                </subviews>
                                <color key="backgroundColor" red="0.9411764706" green="0.9411764706" blue="0.9411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <constraints>
                                    <constraint firstItem="l1v-8X-9kq" firstAttribute="top" secondItem="k1v-7X-8kq" secondAttribute="top" constant="0.0" id="a3c-0Y-bkq"/>
                                    <constraint firstAttribute="bottom" secondItem="l1v-8X-9kq" secondAttribute="bottom" constant="0.0" id="b3c-1Y-ckq"/>
                                    <constraint firstItem="l1v-8X-9kq" firstAttribute="leading" secondItem="k1v-7X-8kq" secondAttribute="leading" constant="10" id="c3c-2Y-dkq"/>
                                    <constraint firstItem="m1v-9X-akq" firstAttribute="trailing" secondItem="k1v-7X-8kq" secondAttribute="trailing" constant="-10" id="d3c-3Y-ekq"/>
                                    <constraint firstAttribute="bottom" secondItem="m1v-9X-akq" secondAttribute="bottom" constant="30" id="e3c-4Y-fkq"/>
                                    <constraint firstItem="m1v-9X-akq" firstAttribute="top" secondItem="k1v-7X-8kq" secondAttribute="top" constant="30" id="f3c-5Y-gkq"/>
                                    <constraint firstAttribute="trailing" secondItem="l1v-8X-9kq" secondAttribute="trailing" constant="70" id="g3c-6Y-hkq"/>
                                    <constraint firstItem="m1v-9X-akq" firstAttribute="leading" secondItem="l1v-8X-9kq" secondAttribute="trailing" constant="10" id="h3c-7Y-ikq"/>
                                </constraints>
                                <userLabel value="商品3容器"/>
                                <cornerRadius key="cornerRadius" value="8"/>
                            </view>

                            <!-- 恢复购买按钮 -->
                            <button userLabel="恢复购买" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="n1v-8X-jkq">
                                <rect key="frame" x="37.5" y="494" width="300" height="40"/>
                                <autoresizingMask key="autoresizingMask"/>
                                <fontDescription key="fontDescription" type="system" pointSize="16" weight="medium"/>
                                <state key="normal" title="恢复购买"/>
                                <color key="backgroundColor" red="0.6862745098" green="0.6862745098" blue="0.6862745098" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                            </button>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <!-- 标题约束 -->
                            <constraint firstItem="e1v-1X-jkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="a4c-9X-kkq"/>
                            <constraint firstItem="e1v-1X-jkq" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="0.0" id="b4c-0Y-lkq"/>
                            <constraint firstAttribute="trailing" secondItem="e1v-1X-jkq" secondAttribute="trailing" constant="37.5" id="c4c-1Y-mkq"/>

                            <!-- 状态约束 -->
                            <constraint firstItem="e2r-2Y-lkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="d4c-2Y-nkq"/>
                            <constraint firstItem="e2r-2Y-lkq" firstAttribute="top" secondItem="e1v-1X-jkq" secondAttribute="bottom" constant="10" id="e4c-3Y-okq"/>
                            <constraint firstAttribute="trailing" secondItem="e2r-2Y-lkq" secondAttribute="trailing" constant="37.5" id="f4c-4Y-pkq"/>

                            <!-- 商品1约束 -->
                            <constraint firstItem="d1v-3X-mkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="g4c-5Y-qkq"/>
                            <constraint firstItem="d1v-3X-mkq" firstAttribute="top" secondItem="e2r-2Y-lkq" secondAttribute="bottom" constant="20" id="h4c-6Y-rkq"/>
                            <constraint firstAttribute="trailing" secondItem="d1v-3X-mkq" secondAttribute="trailing" constant="37.5" id="i4c-7Y-skq"/>
                            <constraint firstAttribute="width" secondItem="d1v-3X-mkq" secondAttribute="width" multiplier="1" constant="0.0" id="j4c-8Y-tkq"/>
                            <constraint firstItem="d1v-3X-mkq" firstAttribute="height" constant="100" id="k4c-9Y-ukq"/>

                            <!-- 商品2约束 -->
                            <constraint firstItem="h1v-4X-xkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="l4c-0Z-vkq"/>
                            <constraint firstItem="h1v-4X-xkq" firstAttribute="top" secondItem="d1v-3X-mkq" secondAttribute="bottom" constant="10" id="m4c-1Z-wkq"/>
                            <constraint firstAttribute="trailing" secondItem="h1v-4X-xkq" secondAttribute="trailing" constant="37.5" id="n4c-2Z-xkq"/>
                            <constraint firstItem="h1v-4X-xkq" firstAttribute="height" constant="100" id="o4c-3Z-ykq"/>

                            <!-- 商品3约束 -->
                            <constraint firstItem="k1v-7X-8kq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="p4c-4Z-zkq"/>
                            <constraint firstItem="k1v-7X-8kq" firstAttribute="top" secondItem="h1v-4X-xkq" secondAttribute="bottom" constant="10" id="q4c-5Z-0kq"/>
                            <constraint firstAttribute="trailing" secondItem="k1v-7X-8kq" secondAttribute="trailing" constant="37.5" id="r4c-6Z-1kq"/>
                            <constraint firstItem="k1v-7X-8kq" firstAttribute="height" constant="100" id="s4c-7Z-2kq"/>

                            <!-- 恢复购买按钮约束 -->
                            <constraint firstItem="n1v-8X-jkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="37.5" id="t4c-8Z-3kq"/>
                            <constraint firstItem="n1v-8X-jkq" firstAttribute="top" secondItem="k1v-7X-8kq" secondAttribute="bottom" constant="20" id="u4c-9Z-4kq"/>
                            <constraint firstAttribute="trailing" secondItem="n1v-8X-jkq" secondAttribute="trailing" constant="37.5" id="v4c-0a-5kq"/>
                            <constraint firstItem="n1v-8X-jkq" firstAttribute="height" constant="40" id="w4c-1a-6kq"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="product1Label" destination="f1v-4X-nkq" id="con-0a-7kq"/>
                        <outlet property="product2Label" destination="i1v-5X-ykq" id="con-1a-8kq"/>
                        <outlet property="product3Label" destination="l1v-8X-9kq" id="con-2a-9kq"/>
                        <outlet property="product1Button" destination="g1v-5X-okq" id="con-3a-akq"/>
                        <outlet property="product2Button" destination="j1v-6X-zkq" id="con-4a-bkq"/>
                        <outlet property="product3Button" destination="m1v-9X-akq" id="con-5a-ckq"/>
                        <outlet property="statusLabel" destination="e2r-2Y-lkq" id="con-6a-dkq"/>
                        <action selector="purchaseProduct1:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-7a-ekq">
                            <sender slot="source" id="g1v-5X-okq"/>
                        </action>
                        <action selector="purchaseProduct2:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-8a-fkq">
                            <sender slot="source" id="j1v-6X-zkq"/>
                        </action>
                        <action selector="purchaseProduct3:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-9a-gkq">
                            <sender slot="source" id="m1v-9X-akq"/>
                        </action>
                        <action selector="restorePurchases:" destination="BYZ-38-t0r" eventType="touchUpInside" id="con-0b-hkq">
                            <sender slot="source" id="n1v-8X-jkq"/>
                        </action>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFilesOwner" id="dkx-z0-nzr" sceneMemberID="filesOwner"/>
                <placeholder placeholderIdentifier="IBFirstResponder" id="26j-XQyn0c" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="137.5" y="102.5"/>
        </scene>
    </scenes>
</document>

建立info.list创建Info.plist文件,配置应用程序的基本信息,包括应用名称、版本、Bundle Identifier等。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleDisplayName</key>
    <string>iOSInAppPurchaseDemo</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>Liu.GameTest</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
</dict>
</plist>

创建LaunchScreen.storyboard文件,作为应用程序的启动界面

xml 复制代码
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" launchScreen="YES">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21504"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <label userLabel="启动标题" text="iOS内购演示" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-1X-jkq">
                                <rect key="frame" x="75" y="276.5" width="225" height="40"/>
                                <fontDescription key="fontDescription" type="system" pointSize="30" weight="medium"/>
                                <color key="textColor" red="0.1294117647" green="0.1294117647" blue="0.1294117647" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <label userLabel="启动副标题" text="Loading..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e2v-2X-lkq">
                                <rect key="frame" x="75" y="326.5" width="225" height="20"/>
                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
                                <color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.5764705882" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="e1v-1X-jkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="75" id="a1c-3X-mkq"/>
                            <constraint firstItem="e1v-1X-jkq" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" constant="-25" id="b1c-4X-nkq"/>
                            <constraint firstAttribute="trailing" secondItem="e1v-1X-jkq" secondAttribute="trailing" constant="75" id="c1c-5X-okq"/>
                            <constraint firstItem="e2v-2X-lkq" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="75" id="d1c-6X-pkq"/>
                            <constraint firstItem="e2v-2X-lkq" firstAttribute="top" secondItem="e1v-1X-jkq" secondAttribute="bottom" constant="10" id="e1c-7X-qkq"/>
                            <constraint firstAttribute="trailing" secondItem="e2v-2X-lkq" secondAttribute="trailing" constant="75" id="f1c-8X-rkq"/>
                        </constraints>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFilesOwner" id="dkx-z0-nzr" sceneMemberID="filesOwner"/>
                <placeholder placeholderIdentifier="IBFirstResponder" id="26j-XQyn0c" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="137.5" y="102.5"/>
        </scene>
    </scenes>
</document>

aunchScreen.storyboard已创建完成。现在需要创建Xcode项目配置文件project.pbxproj,这是项目的核心配置文件,定义了项目结构和构建设置。

ini 复制代码
// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 46;
    objects = {
        /* Begin PBXBuildFile section */
        19786D9D1E8E4F5A00D94E8C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 19786D9C1E8E4F5A00D94E8C /* AppDelegate.m */; };
        19786D9F1E8E4F5A00D94E8C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 19786D9E1E8E4F5A00D94E8C /* ViewController.m */; };
        19786DA31E8E4F5A00D94E8C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19786DA21E8E4F5A00D94E8C /* Main.storyboard */; };
        19786DA51E8E4F5A00D94E8C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19786DA41E8E4F5A00D94E8C /* LaunchScreen.storyboard */; };
        19786DA71E8E4F5A00D94E8C /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 19786DA61E8E4F5A00D94E8C /* Info.plist */; };
        /* End PBXBuildFile section */

        /* Begin PBXFileReference section */
        19786D9A1E8E4F5A00D94E8C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iOSInAppPurchaseDemo/AppDelegate.h; sourceTree = "<group>"; };
        19786D9C1E8E4F5A00D94E8C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSInAppPurchaseDemo/AppDelegate.m; sourceTree = "<group>"; };
        19786D9D1E8E4F5A00D94E8C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iOSInAppPurchaseDemo/ViewController.h; sourceTree = "<group>"; };
        19786D9E1E8E4F5A00D94E8C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSInAppPurchaseDemo/ViewController.m; sourceTree = "<group>"; };
        19786DA21E8E4F5A00D94E8C /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = iOSInAppPurchaseDemo/Base.lproj/Main.storyboard; sourceTree = "<group>"; };
        19786DA41E8E4F5A00D94E8C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = iOSInAppPurchaseDemo/Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
        19786DA61E8E4F5A00D94E8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = iOSInAppPurchaseDemo/Info.plist; sourceTree = "<group>"; };
        19786DA91E8E4F5A00D94E8C /* iOSInAppPurchaseDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSInAppPurchaseDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
        19786DAA1E8E4F5A00D94E8C /* SubscriptionKey_Q9W29CG749.p8 */ = {isa = PBXFileReference; lastKnownFileType = text; path = SubscriptionKey_Q9W29CG749.p8; sourceTree = "<group>"; };
        /* End PBXFileReference section */

        /* Begin PBXGroup section */
        19786D931E8E4F5A00D94E8C = {isa = PBXGroup; children = (
            19786D9A1E8E4F5A00D94E8C /* AppDelegate.h */,
            19786D9C1E8E4F5A00D94E8C /* AppDelegate.m */,
            19786D9D1E8E4F5A00D94E8C /* ViewController.h */,
            19786D9E1E8E4F5A00D94E8C /* ViewController.m */,
            19786DA01E8E4F5A00D94E8C /* Base.lproj */,
            19786DA61E8E4F5A00D94E8C /* Info.plist */,
        ); path = iOSInAppPurchaseDemo; sourceTree = "<group>"; };
        19786DA01E8E4F5A00D94E8C /* Base.lproj */ = {isa = PBXGroup; children = (
            19786DA21E8E4F5A00D94E8C /* Main.storyboard */,
            19786DA41E8E4F5A00D94E8C /* LaunchScreen.storyboard */,
        ); path = Base.lproj; sourceTree = "<group>"; };
        19786D921E8E4F5A00D94E8C = {isa = PBXGroup; children = (
            19786D931E8E4F5A00D94E8C /* iOSInAppPurchaseDemo */,
            19786DA91E8E4F5A00D94E8C /* Products */,
        ); sourceTree = "<group>"; };
        19786DA91E8E4F5A00D94E8C = {isa = PBXGroup; children = (
            19786DA91E8E4F5A00D94E8C /* iOSInAppPurchaseDemo.app */,
        ); name = Products; sourceTree = "<group>"; };
        /* End PBXGroup section */

        /* Begin PBXNativeTarget section */
        19786D991E8E4F5A00D94E8C = {isa = PBXNativeTarget; buildConfigurationList = 19786DAE1E8E4F5A00D94E8C /* Build configuration list for PBXNativeTarget "iOSInAppPurchaseDemo" */; buildPhases = (
            19786D951E8E4F5A00D94E8C /* Sources */,
            19786D961E8E4F5A00D94E8C /* Resources */,
            19786D971E8E4F5A00D94E8C /* Frameworks */,
        ); buildRules = (
        ); dependencies = (
        ); name = iOSInAppPurchaseDemo; productName = iOSInAppPurchaseDemo; productReference = 19786DA91E8E4F5A00D94E8C /* iOSInAppPurchaseDemo.app */; productType = "com.apple.product-type.application"; };
        /* End PBXNativeTarget section */

        /* Begin PBXProject section */
        19786D911E8E4F5A00D94E8C = {isa = PBXProject; attributes = {
            LastSwiftUpdateCheck = 0900;
            LastUpgradeCheck = 0900;
            ORGANIZATIONNAME = "Your Organization";
            targetAttributes = {
                19786D991E8E4F5A00D94E8C = {
                    CreatedOnToolsVersion = 9.0;
                    LastSwiftMigration = 0900;
                };
            };
        };
            buildConfigurationList = 19786D901E8E4F5A00D94E8C /* Build configuration list for PBXProject "iOSInAppPurchaseDemo" */;
            compatibilityVersion = "Xcode 3.2";
            developmentRegion = "zh_CN";
            hasScannedForEncodings = 0;
            mainGroup = 19786D921E8E4F5A00D94E8C;
            productRefGroup = 19786DA91E8E4F5A00D94E8C /* Products */;
            projectDirPath = "";
            projectRoot = "";
            targets = (
                19786D991E8E4F5A00D94E8C /* iOSInAppPurchaseDemo */,
            );
        };
        /* End PBXProject section */

        /* Begin PBXResourcesBuildPhase section */
        19786D961E8E4F5A00D94E8C /* Resources */ = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647;
            files = (
                19786DA31E8E4F5A00D94E8C /* Main.storyboard in Resources */,
                19786DA51E8E4F5A00D94E8C /* LaunchScreen.storyboard in Resources */,
                19786DA71E8E4F5A00D94E8C /* Info.plist in Resources */,
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
        /* End PBXResourcesBuildPhase section */

        /* Begin PBXSourcesBuildPhase section */
        19786D951E8E4F5A00D94E8C /* Sources */ = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647;
            files = (
                19786D9D1E8E4F5A00D94E8C /* AppDelegate.m in Sources */,
                19786D9F1E8E4F5A00D94E8C /* ViewController.m in Sources */,
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
        /* End PBXSourcesBuildPhase section */

        /* Begin PBXFrameworksBuildPhase section */
        19786D971E8E4F5A00D94E8C /* Frameworks */ = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647;
            files = (
                19786DB01E8E4F5A00D94E8C /* StoreKit.framework in Frameworks */,
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
        /* End PBXFrameworksBuildPhase section */

        /* Begin PBXBuildFile for Frameworks */
        19786DB01E8E4F5A00D94E8C /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 19786DB11E8E4F5A00D94E8C /* StoreKit.framework */; };
        /* End PBXBuildFile for Frameworks */

        /* Begin PBXFileReference for Frameworks */
        19786DB11E8E4F5A00D94E8C /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
        /* End PBXFileReference for Frameworks */

        /* Begin PBXBuildConfiguration section */
        19786D9B1E8E4F5A00D94E8C /* Debug */ = {isa = PBXBuildConfiguration; buildSettings = {
            ALWAYS_SEARCH_USER_PATHS = NO;
            CLANG_ANALYZER_NONNULL = YES;
            CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
            CLANG_CXX_LIBRARY = "libc++";
            CLANG_ENABLE_MODULES = YES;
            CLANG_ENABLE_OBJC_ARC = YES;
            CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
            CLANG_WARN_BOOL_CONVERSION = YES;
            CLANG_WARN_COMMA = YES;
            CLANG_WARN_CONSTANT_CONVERSION = YES;
            CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
            CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
            CLANG_WARN_EMPTY_BODY = YES;
            CLANG_WARN_ENUM_CONVERSION = YES;
            CLANG_WARN_INFINITE_RECURSION = YES;
            CLANG_WARN_INT_CONVERSION = YES;
            CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
            CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
            CLANG_WARN_OBJC_ROOT_CLASS = YES;
            CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
            CLANG_WARN_STRICT_PROTOTYPES = YES;
            CLANG_WARN_SUSPICIOUS_MOVE = YES;
            CLANG_WARN_UNREACHABLE_CODE = YES;
            CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
            COPY_PHASE_STRIP = NO;
            DEBUG_INFORMATION_FORMAT = dwarf;
            ENABLE_STRICT_OBJC_MSGSEND = YES;
            ENABLE_TESTABILITY = YES;
            GCC_C_LANGUAGE_STANDARD = gnu99;
            GCC_DYNAMIC_NO_PIC = NO;
            GCC_NO_COMMON_BLOCKS = YES;
            GCC_OPTIMIZATION_LEVEL = 0;
            GCC_PRECOMPILE_PREFIX_HEADER = NO;
            GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
            GCC_WARN_ABOUT_RETURN_TYPE = YES;
            GCC_WARN_UNDECLARED_SELECTOR = YES;
            GCC_WARN_UNINITIALIZED_AUTOS = YES;
            GCC_WARN_UNUSED_FUNCTION = YES;
            GCC_WARN_UNUSED_VARIABLE = YES;
            IPHONEOS_DEPLOYMENT_TARGET = 12.0;
            MTL_ENABLE_DEBUG_INFO = YES;
            MTL_FAST_MATH = YES;
            ONLY_ACTIVE_ARCH = YES;
            SDKROOT = iphoneos;
            TARGETED_DEVICE_FAMILY = "1,2";
        };
            name = Debug;
        };
        19786D9C1E8E4F5A00D94E8C /* Release */ = {isa = PBXBuildConfiguration; buildSettings = {
            ALWAYS_SEARCH_USER_PATHS = NO;
            CLANG_ANALYZER_NONNULL = YES;
            CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
            CLANG_CXX_LIBRARY = "libc++";
            CLANG_ENABLE_MODULES = YES;
            CLANG_ENABLE_OBJC_ARC = YES;
            CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
            CLANG_WARN_BOOL_CONVERSION = YES;
            CLANG_WARN_COMMA = YES;
            CLANG_WARN_CONSTANT_CONVERSION = YES;
            CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
            CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
            CLANG_WARN_EMPTY_BODY = YES;
            CLANG_WARN_ENUM_CONVERSION = YES;
            CLANG_WARN_INFINITE_RECURSION = YES;
            CLANG_WARN_INT_CONVERSION = YES;
            CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
            CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
            CLANG_WARN_OBJC_ROOT_CLASS = YES;
            CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
            CLANG_WARN_STRICT_PROTOTYPES = YES;
            CLANG_WARN_SUSPICIOUS_MOVE = YES;
            CLANG_WARN_UNREACHABLE_CODE = YES;
            CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
            COPY_PHASE_STRIP = YES;
            DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
            ENABLE_NS_ASSERTIONS = NO;
            ENABLE_STRICT_OBJC_MSGSEND = YES;
            GCC_C_LANGUAGE_STANDARD = gnu99;
            GCC_NO_COMMON_BLOCKS = YES;
            GCC_PRECOMPILE_PREFIX_HEADER = NO;
            GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
            GCC_WARN_ABOUT_RETURN_TYPE = YES;
            GCC_WARN_UNDECLARED_SELECTOR = YES;
            GCC_WARN_UNINITIALIZED_AUTOS = YES;
            GCC_WARN_UNUSED_FUNCTION = YES;
            GCC_WARN_UNUSED_VARIABLE = YES;
            IPHONEOS_DEPLOYMENT_TARGET = 12.0;
            MTL_ENABLE_DEBUG_INFO = NO;
            MTL_FAST_MATH = YES;
            SDKROOT = iphoneos;
            TARGETED_DEVICE_FAMILY = "1,2";
        };
            name = Release;
        };
        19786DAF1E8E4F5A00D94E8C /* Debug */ = {isa = PBXBuildConfiguration; buildSettings = {
            ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
            CLANG_ENABLE_MODULES = YES;
            INFOPLIST_FILE = "iOSInAppPurchaseDemo/Info.plist";
            IPHONEOS_DEPLOYMENT_TARGET = 12.0;
            PRODUCT_BUNDLE_IDENTIFIER = Liu.GameTest;
            PRODUCT_NAME = iOSInAppPurchaseDemo;
            TARGETED_DEVICE_FAMILY = "1,2";
        };
            name = Debug;
        };
        19786DB01E8E4F5A00D94E8C /* Release */ = {isa = PBXBuildConfiguration; buildSettings = {
            ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
            CLANG_ENABLE_MODULES = YES;
            INFOPLIST_FILE = "iOSInAppPurchaseDemo/Info.plist";
            IPHONEOS_DEPLOYMENT_TARGET = 12.0;
            PRODUCT_BUNDLE_IDENTIFIER = Liu.GameTest;
            PRODUCT_NAME = iOSInAppPurchaseDemo;
            TARGETED_DEVICE_FAMILY = "1,2";
        };
            name = Release;
        };
        /* End PBXBuildConfiguration section */

        /* Begin XCConfigurationList section */
        19786D901E8E4F5A00D94E8C /* Build configuration list for PBXProject "iOSInAppPurchaseDemo" */ = {isa = XCConfigurationList; buildConfigurations = (
            19786D9B1E8E4F5A00D94E8C /* Debug */,
            19786D9C1E8E4F5A00D94E8C /* Release */,
        ); defaultConfigurationIsVisible = 0;
            defaultConfigurationName = Release;
        };
        19786DAE1E8E4F5A00D94E8C /* Build configuration list for PBXNativeTarget "iOSInAppPurchaseDemo" */ = {isa = XCConfigurationList; buildConfigurations = (
            19786DAF1E8E4F5A00D94E8C /* Debug */,
            19786DB01E8E4F5A00D94E8C /* Release */,
        ); defaultConfigurationIsVisible = 0;
            defaultConfigurationName = Release;
        };
        /* End XCConfigurationList section */
    };
    rootObject = 19786D911E8E4F5A00D94E8C;
}

核心内容参考已经 写完,更多内容可以参考 开源出来的 文件

整个demo实例:

iOS 内购开发 (In-App Purchase) Objective-C Storyboard 实例

项目介绍

这是一个基于 Objective-C 和 Storyboard 的 iOS 内购开发示例项目,展示了如何在 iOS 应用中实现苹果应用内购买功能。

功能特性

  • 支持多个内购商品的展示和购买
  • 实现商品信息请求与展示
  • 完整的购买流程处理
  • 恢复已购买商品功能
  • 错误处理和状态反馈

技术架构

  • 开发语言: Objective-C
  • 界面设计: Storyboard
  • 内购框架: StoreKit.framework
  • 最低支持系统: iOS 12.0
  • Bundle Identifier: Liu.GameTest

项目结构

bash 复制代码
iOSInAppPurchaseDemo/
├── AppDelegate.h           # 应用程序委托头文件
├── AppDelegate.m           # 应用程序委托实现
├── ViewController.h        # 主视图控制器头文件
├── ViewController.m        # 主视图控制器实现(包含内购核心逻辑)
├── Info.plist              # 应用配置文件
└── Base.lproj/
    ├── Main.storyboard     # 主界面故事板
    └── LaunchScreen.storyboard # 启动界面故事板

内购商品配置

项目中配置了三个测试商品ID:

  • 001Liu
  • 002Liu
  • 003Liu

安装与运行

  1. 克隆或下载本项目
  2. 打开 iOSInAppPurchaseDemo.xcodeproj 文件
  3. 确保已在 Xcode 中配置开发者账号
  4. 在真机或模拟器上运行项目

注意事项

  1. 内购功能需要在真机上进行完整测试
  2. 在 App Store Connect 中配置相应的内购项目才能进行真实购买
  3. 项目包含测试用的 SubscriptionKey_Q9W29CG749.p8 文件
  4. 实际项目中请替换为自己的密钥文件和 Bundle Identifier

使用说明

  1. 启动应用后,系统会自动请求商品信息
  2. 点击对应商品的购买按钮进行购买
  3. 输入 Apple ID 密码完成支付
  4. 购买成功后会显示成功提示
  5. 可以点击"恢复购买"按钮恢复已购买的商品

开发提示

  • 确保在 Info.plist 中正确配置了应用的 Bundle Identifier
  • 在 App Store Connect 中创建相应的应用和内购项目
  • 测试内购时使用沙盒测试账号
  • 处理各种交易状态和错误情况

License

© 2025 优雅草科技. All rights reserved.

相关推荐
用泥种荷花2 小时前
【web音频学习(一)】音频基础
前端
Mintopia2 小时前
Next.js自建部署:Docker + PM2 + Nginx
前端·javascript·全栈
艾小码2 小时前
还在重复造轮子?3个Vue3组合函数让你开发效率翻倍!
前端·javascript·vue.js
一枚前端小能手2 小时前
🎨 CSS-in-JS到底香不香?性能陷阱让我重新思考了
前端·javascript·css
章丸丸2 小时前
Tube - Infinite Loading
前端·next.js
我是日安2 小时前
从零到一打造 Vue3 响应式系统 Day 3 - 订阅者模式:响应式设计基础
前端·vue.js
拜无忧2 小时前
【知识点】vue3不常用api总结-针对前端中级-进阶
前端·vue.js·性能优化
Mintopia2 小时前
AIGC在电商Web端的个性化推荐技术实现
前端·javascript·aigc
双向332 小时前
前端性能优化:Webpack Tree Shaking 的实践与踩坑
前端