Highlight
Wallet は 2025 年に
upcomingPassInformationを導入し、1 枚のパスでツアー全体やシーズン全体の複数イベントを収納できるようになりました。各イベントは独立したidentifier、isActiveステータス、会場のセマンティクス、詳細ページを持ちます。
主要内容
チケット系 App の長年の課題は、シーズン 20 試合分のチケットがあるとユーザーの Wallet に 20 枚のパスが積み上がってしまうことでした。月別カレンダー表示は同じチームのロゴで埋め尽くされ、目当ての試合を探すには日付を頼りにするしかありません。昨年 Wallet が導入した Poster Event Ticket はビジュアルを刷新したものの、データモデル自体は据え置きで、各試合は依然として独立したパスのままでした。今年の upcomingPassInformation では、いよいよデータ構造そのものに手が入りました。1 枚のパスがイベント配列を持ち、各イベントが固有の ID、日付、会場、出演者、座席番号、独立したイベントガイドを保持します。ユーザーがパスを開くと「行程リスト」が表示され、タップすると個別イベントの詳細ページに遷移します。開発者の移行コストは主に 2 点に集約されます。すなわち、イベントの追加・削除に使う安定した identifier と、入場可能な現在のイベントを示す isActive フィールドです。
搭乗券側の変更はさらに踏み込んでいます。Apple はフライトステータスのトラッキングをシステムレベルの Apple Flight Service に直接組み込みました。パス追加時に Wallet は airlineCode、flightNumber、originalDepartureDate という 3 つのセマンティックフィールドを使ってフライトを検索し、その後の搭乗ゲート変更、遅延、ターミナル変更などはすべてシステム側からプッシュされます。開発者のバックエンドで APNs ポーリングに頼る必要はもうありません。搭乗時刻も個別に管理する必要はなく、Wallet はパス追加の瞬間に「出発時刻 - 搭乗時刻」の差分を記録しておき、出発時刻が公式システムから更新されると、搭乗時刻もこの差分に従って自動的にスライドします。新しい Live Activity と組み合わせれば、ユーザーはロックスクリーンや Dynamic Island でフライトの着陸まで追い続けることができ、さらにこの Live Activity を Messages で出迎えに来る人へ共有することもできます。
もう一つ見落とされがちな改良が、パスの自動追加です。PKPassLibrary に backgroundAddPasses という一度きりの認可機能が追加され、認可後は addPasses(_:) を呼び出すだけでパスを直接 Wallet に投入でき、通知 1 件でユーザーに知らせるだけで済みます。月額パス、通勤定期、頻繁に出張する航空会社の App にとっては、「App を開く → 注文を探す → Wallet に追加をタップする」という導線から少なくとも 2 回のタップを削減できます。
詳細
複数イベント対応チケットの pass.json 構造(02:04)。Upcoming events は pass.json のトップレベルにある upcomingPassInformation 配列に書き込みます。各要素は完全なイベントオブジェクトです。
{
"upcomingPassInformation": [
{
"type": "event",
"identifier": "tour-2025-tokyo",
"displayName": "Tokyo Night",
"date": "2025-09-12T19:00:00+09:00",
"isActive": true,
"semantics": {
"venueName": "Tokyo Dome",
"venuePlaceID": "...",
"venueLocation": { "latitude": 35.7056, "longitude": 139.7519 },
"seats": [{ "seatSection": "A", "seatRow": "12", "seatNumber": "8" }]
},
"URLs": {
"purchaseParkingURL": "https://example.com/parking/tokyo"
},
"images": {
"headerImage": {
"1x": "https://example.com/[email protected]",
"2x": "https://example.com/[email protected]",
"3x": "https://example.com/[email protected]"
}
}
}
]
}
ポイント:
identifierはパス内でグローバルに一意かつ安定している必要があります。後続のパスアップデートでこのイベントを追加・削除・更新する際にこの値を頼りにします。isActiveはイベント開始時に true、終了後に false にします。これにより、Wallet のリスト上でこのイベントが「これから始まるイベント」としてハイライト表示されるかどうかが制御されます。semantics内のvenueName/venuePlaceID/venueLocationは詳細ページ上部の会場カードと地図ボタンを直接駆動します。構造は単一イベントの Poster Event Ticket とまったく同じですが、外側のパスにある同名フィールドを自動継承しない点に注意してください。各イベントごとに繰り返し記述する必要があります。images.headerImageは 1x / 2x / 3x の 3 解像度を必ず提供してください。upcoming event の詳細ページは iOS と watchOS の両方に表示されるためです。- このイベントで外側のパスの会場マップを再利用したい場合は、
venueMap.reuseExistingを true に設定してください。設定しないと Wallet は会場マップを表示しません。
強化された搭乗券のセマンティクス層(09:00)。Wallet は 3 つのセマンティックフィールドでフライトを特定し、boarding と departure の時間差から搭乗時刻を自動的に維持します。pass.json には元の値だけ書いておけば、あとは Apple Flight Service が引き受けます。
{
"semantics": {
"airlineCode": "CA",
"flightNumber": "983",
"originalDepartureDate": "2025-09-12T08:30:00+08:00",
"currentDepartureDate": "2025-09-12T08:30:00+08:00",
"originalBoardingDate": "2025-09-12T07:50:00+08:00",
"currentBoardingDate": "2025-09-12T07:50:00+08:00",
"passengerServiceSSRs": ["SVAN", "WCOB"],
"airlineLoungePlaceID": "..."
},
"purchaseLoungeAccessURL": "https://example.com/lounge"
}
ポイント:
airlineCode + flightNumber + originalDepartureDateはワンセットです。1 つでも欠けると Wallet は Apple Flight Service でフライトを発見できず、自動トラッキング全体が静的表示にフォールバックします。originalDepartureDateは常にチケットに印字されている時刻です。遅延が発生しても書き換えてはいけません。遅延はcurrentDepartureDateのみを更新してください(09:45)。originalBoardingDateとoriginalDepartureDateはパス追加の瞬間に「搭乗所要時間」の算出に使われます。その後は出発時刻が Apple 側から更新されるたびに、搭乗時刻もこの差分に従って自動でスライドします。自前で上書きしたい場合はcurrentBoardingDateを使ってください。passengerServiceSSRsは IATA 標準の SSR コード(例:SVAN介助犬、WCOB機内車椅子)を受け付けます。表示テキストは Wallet がレンダリングするため、航空会社をまたいでも統一された見た目になります。airlineLoungePlaceIDをpurchaseLoungeAccessURLと組み合わせて指定すると、Wallet はボタンの下に地図プレビューを追加で描画します(14:56)。URL だけ与えて placeID を省くと、ボタンが孤立した見た目になってしまいます。
パスを自動追加するための API(17:35)。フローはプッシュ通知の許諾要求に近いですが、ダイアログを出せるのは 1 回限りです。
import PassKit
import SwiftUI
struct AddPassToWalletButton: View {
let pass: PKPass
var body: some View {
Button("Add to Wallet") {
Task {
try? await PKPassLibrary().requestAuthorization(for: .backgroundAddPasses)
}
}
.task {
let status = PKPassLibrary().authorizationStatus(for: .backgroundAddPasses)
if status == .authorized {
try? await PKPassLibrary().addPasses([pass])
}
}
}
}
ポイント:
requestAuthorization(for: .backgroundAddPasses)は非同期かつ一度限りです。ユーザーが許可・拒否のいずれかを選んだ後に再度呼び出してもダイアログは再表示されません(18:12)。そのため、ユーザーが同意してくれそうなタイミング、たとえば購入完了の直後に呼び出すべきです。authorizationStatus(for:)は UI を伴わないため、.taskやonAppearの中でそのまま読み取って構いません。addPasses(_:)は配列を受け取り、一度に複数のパスを追加できます。「年間パス購入で月別カードを 12 枚一括発行」のようなシナリオに最適です。- 認可されていても、ユーザーはシステム設定からいつでもこの権限をオフにできます。App 側でブロックする手段はないため、認可ステータスは毎回チェックする必要があります。
- バックグラウンド追加が成功すると Wallet がシステム通知を発行します。アプリ内トーストを重ねて出さないよう、重複した通知でユーザーを煩わせないでください。
重要ポイント
-
何をするか: 既存の「1 イベント = 1 パス」モデルを
upcomingPassInformationへ移行する。 なぜ取り組む価値があるか: ユーザーの Wallet に散らばっていた同じチームのシーズンチケット 20 枚が 1 枚にまとまり、詳細ページに出演者、会場マップ、駐車券などの関連情報をすべて格納できるようになります。1 枚あたりの保有率と接触率を高められます。 どう始めるか: まずテスト用パスにイベントを 2〜3 件追加し、Wallet 上での描画を確認します。続いてパスアップデート時のidentifierによる追加・削除挙動が想定通りかを検証してから、バックエンドのテンプレートを一括で改修してください。 -
何をするか: 搭乗券のセマンティクス層を埋め、Apple Flight Service のトラッキングを有効化する。 なぜ取り組む価値があるか: 自前の「フライト変更プッシュ」のラインナップ(ポーリング + APNs + 同期)をオフにできるうえ、Live Activity と家族・友人への共有機能も自動的に手に入ります。エンジニアリング的には引き算でありながら、ユーザー体験は足し算になります。 どう始めるか: 最初のステップとして
airlineCode、flightNumber、originalDepartureDate、currentDepartureDateの 4 フィールドだけ埋めます。次のステップで SSR コードとサービス URL を追加します。リリース後は実フライトを使って Apple Flight Service のデータ精度を突き合わせ、国内線の場合は二重チャネルでしばらく並走させてから自前プッシュを段階的に廃止することをおすすめします。 -
何をするか: 購入成功の瞬間に
backgroundAddPassesの認可を要求する。 なぜ取り組む価値があるか: 「Wallet に追加」の成功率を、2 ステップのインタラクションで生じる離脱からほぼ完全に取り戻せます。月額パス、通勤定期、ホテルチェーンの会員カードなど「ユーザー側もそもそも Wallet に入れたい」と考えているシーンで効果が最大になります。 どう始めるか: 購入完了画面に「Wallet に自動追加する」スイッチを明示的に置き、ユーザー側のアクションをトリガーにして認可を要求します。App 起動時にサイレントで呼び出すのは避けてください。1 度きりのダイアログ機会を浪費するだけです。 -
何をするか:
passengerServiceSSRsで搭乗ラベルを標準化する。 なぜ取り組む価値があるか: 介助犬、車椅子、特別食といった情報を IATA 標準コードに揃えれば、航空会社をまたいでも Wallet 上の表示が統一されます。地上スタッフが繰り返し確認する手間を減らせるうえ、自前で多言語の文言を保守する必要もなくなります。 どう始めるか: バックエンドの注文システムにある特別リクエストフィールドを IATA SSR コードにマッピングし、pass.json のsemantics.passengerServiceSSRs配列に書き込むだけです。
関連セッション
- Discover Apple-Hosted Background Assets — Apple がホスティングするバックグラウンドアセットのダウンロード。大容量コンテンツの配信時にサーバー負荷を軽減できます
- Dive deeper into Writing Tools — システムレベルの Writing Tools への深い統合により、App のテキスト入力に校正・リライト機能を持たせられます
- Dive into App Store server APIs for In-App Purchase — App Store サーバー API と通知メカニズムの最新アップデート
- Enhance child safety with PermissionKit — PermissionKit を使って子供のデバイスの通信安全性を強化する
コメント
GitHub Issues · utterances