ハイライト
StoreKit の取引履歴 API がついに完了した消耗品の取引をカバーします。
TransactionとRenewalInfoに新しいcurrencyとpriceフィールドが追加されました。従来の In-App Purchase API は iOS 18 で正式に非推奨とマークされました。
主要内容
アプリ内課金機能を開発する際、消耗品の購入記録の追跡は常に頭の痛い問題でした。ユーザーが 100 コインを購入し、消費して、取引が finish されると、その記録は取引履歴から消えてしまいます。「購入履歴」を表示したり、消耗品の重複購入防止を実装したりする必要がある場合、独自のデータベースを構築して追跡する必要がありました。iOS 18 はついにこの欠落を補いました:取引履歴 API が完了した消耗品の取引を含めるようになりました。Info.plist で SKIncludeConsumableInAppPurchaseHistory を true に設定するだけで、フレームワークは finish 済みかどうかに関わらず、すべての消耗品の取引をクエリ結果に含めます。
もう一つの長年の痛点は価格表示です。App Store Connect で製品の価格を設定できますが、アプリ内では具体的な金額や通貨情報を直接取得できず、サーバー側の中継やハードコーディングに頼るしかありませんでした。iOS 18 は Transaction に currency と price を、RenewalInfo に currency と renewalPrice を追加しました。これらのフィールドは Swift の @backDeployed 属性を通じて iOS 15 まで下位互換があり、古いシステムでも使用できます。
UI 面では、WWDC23 で導入された StoreKit Views が今年大幅に拡張されました。サービスレベル別にサブスクリプションオプションをグループ化する SubscriptionOptionGroup が追加され、compactPicker / pagedPicker / pagedProminentPicker の 3 つの新しいコントロールスタイルが追加されました。購入ボタンの位置を制御する placement API が追加され、SubscriptionStoreControlStyle プロトコルがオープンになり、完全にカスタムなサブスクリプションストアコントロールを作成できるようになりました。Win-back offers は今年新しく導入されたサブスクリプションオファータイプで、特に離脱したユーザーを対象としており、Apple の編集チームによって App Store の Today / Games / Apps タブでフィーチャーされる可能性があります。
詳細
完了した消耗品の取引が履歴 API に含まれる(00:40)
以前は、Transaction.currentEntitlements と Transaction.all はサブスクリプション、非消耗品、および未完了の消耗品の取引のみを返していました。iOS 18 から、Info.plist で以下を設定します:
<key>SKIncludeConsumableInAppPurchaseHistory</key>
<true/>
その後、通常の Transaction.updates リスナーと Transaction.all クエリに完了した消耗品の取引が含まれるようになり、追加のコードは必要ありません。
Transaction と RenewalInfo の新しいフィールド(01:32)
Transaction.currency:購入時に使用された通貨コード(例:"USD")Transaction.price:App Store Connect で設定された価格RenewalInfo.currency:更新時の通貨コードRenewalInfo.renewalPrice:次回更新で請求される金額
これらのフィールドは、Swift の @backDeployed 属性を通じて Xcode 16 でコンパイル時に使用可能になり、実行時の最低サポートは iOS 15 です。
SubscriptionOptionGroup と compactPicker スタイル(04:26)
公式が提供する Destination Video サブスクリプションストアのサンプルコード:
import StoreKit
import SwiftUI
struct DestinationVideoShop: View {
var body: some View {
SubscriptionStoreView(groupID: Self.subscriptionGroupID) {
SubscriptionOptionGroupSet { product in
StreamingPassLevel(product)
} label: { streamingPassLevel in
Text(streamingPassLevel.localizedTitle)
} marketingContent: { streamingPassLevel in
StreamingPassMarketingContent(level: streamingPassLevel)
StreamingPassFeatures(level: streamingPassLevel)
}
}
.subscriptionStoreControlStyle(.compactPicker, placement: .bottomBar)
}
}
キーポイント:
SubscriptionOptionGroupSetはクロージャが返す列挙値(ここではStreamingPassLevel)に基づいて自動的にグループ化します。各一意の値がグループを生成しますlabelクロージャはグループタイトル(例:「Premium」/「Basic」)を提供し、フレームワークは自動的にタブ切り替えとしてレンダリングしますmarketingContentクロージャは、現在アクティブなグループに基づいて異なるマーケティングビューを返します.compactPickerスタイルはサブスクリプションオプションを 1 行の横方向セレクターに圧縮します。placement: .bottomBarはコントロールをボトムバーに固定し、マーケティングコンテンツをスクロールしても常に表示されます
グループスタイルの切り替え(09:06)
デフォルトの .tabs スタイルに加えて、.links スタイルも使用できます:
.subscriptionStoreOptionGroupStyle(.tabs) // タブ切り替え、異なるサービスレベルに適している
.subscriptionStoreOptionGroupStyle(.links) // ナビゲーションリンク、より多くのオプションをシートで展開する場合に適している
カスタムコントロールスタイル(13:41)
iOS 18 は SubscriptionStoreControlStyle プロトコルをオープンにしました。SubscriptionPicker と SubscribeButton プリミティブと組み合わせることで、ゼロから独自のコントロールを構築できます:
import StoreKit
import SwiftUI
struct BadgedPickerControlStyle: SubscriptionStoreControlStyle {
func makeBody(configuration: Configuration) -> some View {
SubscriptionPicker(configuration) { pickerOption in
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text(pickerOption.displayName)
.font(title2.bold())
Text(priceDisplay(for: pickerOption))
if pickerOption.isFamilyShareable {
FamilyShareableBadge()
}
Text(pickerOption.description)
}
Spacer()
SelectionIndicator(pickerOption.isSelected)
}
} confirmation: { option in
SubscribeButton(option)
}
}
}
struct DestinationVideoShop: View {
var body: some View {
SubscriptionStoreView(groupID: Self.subscriptionGroupID) {
SubscriptionPeriodGroupSet { _ in
StreamingPassMarketingContent()
}
}
.subscriptionStoreControlStyle(BadgedPickerControlStyle())
}
}
キーポイント:
SubscriptionStoreControlStyleプロトコルに準拠するには、makeBodyメソッドだけを実装すればよいconfigurationにはコントロールを構築するために必要なすべてのデータが含まれているSubscriptionPickerはフレームワークが提供するプリミティブで、選択状態を管理するpickerOption.displayName/.isFamilyShareable/.descriptionなどのプロパティは、製品メタデータから直接読み取られるSubscribeButton(option)はフレームワークが提供する購入ボタンのプリミティブで、タップすると購入フローがトリガーされるSubscriptionPeriodGroupSetはサブスクリプション期間でグループ化する便利な API
Win-back offers(02:34)
Win-back offers は、サブスクリプションをキャンセルした離脱ユーザー向けです。App Store Connect で資格ルールを設定でき、アプリ内にハードコードされたチェックを書く必要はありません。表示方法は 2 つあります:StoreKit Message API を介してアプリ内にポップアップ表示する(追加のコードは不要)、または Apple の編集チームによって App Store の Today / Games / Apps タブでフィーチャー表示される。
Xcode テストの強化(15:38)
- StoreKit 設定に App Policies セクションが追加され、プライバシーポリシーと利用規約のローカルテストが可能
- サブスクリプショングループの表示名のローカライズテストをサポート
- アプリ内課金画像のテストが可能(
ProductViewのprefersPromotionalIconを介して) - Dialogs 設定でシステムアラートを無効にでき、UI 自動化テストを容易にする
- Transaction Manager が Purchase Intent の送信をサポートし、App Store プロモーション購入をテストできる
- 課金問題メッセージのテストをサポートし、サブスクリプション更新失敗シナリオをシミュレートできる
従来の In-App Purchase API の非推奨化(21:06)
iOS 18 から、SKPaymentQueue などの従来の API は非推奨とマークされ、ユニファイドレシートも同様に非推奨となります。既存のアプリは影響を受けずに引き続き実行できますが、これらの API は新しい機能を受け取らなくなります。StoreKit 2 が唯一、継続的に更新されるパスです。
重要ポイント
-
何をするか:
currencyとpriceフィールドを使って、サーバー側の価格クエリを代替する。なぜ価値があるか:ネットワークリクエストを 1 回減らし、価格表示をよりリアルタイムにし、ユーザーが地域を切り替えてもサーバーの同期を待つ必要がない。どう始めるか:価格を表示する SwiftUI ビューで、Transactionからcurrencyとpriceを読み取り、NumberFormatterで表示をフォーマットする。更新価格も同様にRenewalInfo.renewalPriceから取得する。 -
何をするか:
SubscriptionOptionGroupSetを使ってサブスクリプションストア UI をリファクタリングする。なぜ価値があるか:手書きのグループ化ロジックを宣言的なコードで代替し、タブ切り替えとマーケティングコンテンツの連動はフレームワークが処理するため、コード量が大幅に減少する。どう始めるか:サービスレベルを表す列挙型(例:premium / basic)を定義し、SubscriptionOptionGroupSetのクロージャでその列挙値を返す。フレームワークは自動的に値でグループ化し、タブまたはリンクスタイルをレンダリングする。 -
何をするか:
SKIncludeConsumableInAppPurchaseHistoryを有効にし、独自の消耗品購入追跡ロジックを削除する。なぜ価値があるか:ローカルデータベースのメンテナンスコストを省き、Transaction.allが直接完全な消耗品履歴を返すため、返金と重複防止のロジックがよりシンプルになる。どう始めるか:Info.plist にこのキーを追加してtrueに設定し、Transaction.updatesをリッスンして完了した消耗品の取引を受信する。 -
何をするか:離脱ユーザー向けに win-back offers を統合する。なぜ価値があるか:Apple が App Store の編集フィーチャリング枠であなたのオファーを表示し、無料のトラフィックを獲得できる。資格ルールは App Store Connect で設定され、アプリの更新は不要。どう始めるか:App Store Connect で win-back offer を作成して資格条件を設定し、StoreKit Message API を介してアプリ内で表示するか、サーバー側で App Store Server API を介して配布する。
関連セッション
- Explore App Store server APIs for In-App Purchase — サーバー側の視点:App Store Server API の新しい取引フィールドと通知タイプ
- Implement App Store Offers — win-back offers の完全な実装フローとサンドボックステスト方法
- What’s new in App Store Connect — App Store Connect の新しいフィーチャリングノミネーションとマーケティングアセット生成機能
コメント
GitHub Issues · utterances