Wallet 新特性
What's new in Wallet
2025年6月10日
一句话判断
如果你做票务或航空 App,今年 Wallet 的更新能让你砍掉一半的推送逻辑和通行证管理代码——前提是你愿意把航班状态追踪这类核心体验交给 Apple 的系统服务。
这场 Session 讲了什么
过去几年 Wallet 的更新节奏一直不温不火,今年算是来了个大动作。最核心的变化是”一张票不再只是一张票”——upcomingPassInformation 让一张通行证能装下一整个巡演或赛季,每个场次有自己的详情页、场馆信息和独立状态。这对做了多年”一场一张票”的开发者来说,是个底层数据模型的重写。
登机牌那边的改动更激进。Apple 直接把航班状态追踪接进了系统级的 Apple 航班服务(Apple Flight Service),登机口变了、延误了,系统自己更新,开发者不用再维护那套 APNs 推送 + 后端轮询的逻辑。配合 Live Activity,用户在锁屏和灵动岛就能全程盯着航班状态,甚至可以把行程通过信息分享给接机的人。
还有一个容易被忽略的改动:自动添加通行证 API。PKPassLibrary 新增的 addPasses 方法支持一次授权后自动往 Wallet 塞通行证,把用户从”打开 App → 找到订单 → 点添加到 Wallet”这条路径里解放出来。对月票、周卡这种高频场景影响不小。
值得深挖的点
Apple 航班服务介入后,开发者的推送逻辑怎么办?
登机牌集成 Apple 航班服务是这次更新里最值得玩味的决策。表面上看,这是个纯粹的减负——开发者只需要在创建通行证时填好 airlineCode 和 flightNumber,剩下的航班状态更新全由系统接管。但这里有个 trade-off:你把状态推送的控制权让渡给了 Apple。
过去航空公司的 App 通常维护着一套完整的推送链路:GDS 或航班数据供应商 → 后端服务 → APNs → App 更新通行证。这条链路虽然复杂,但数据是自己的,时机是自己控的,推送内容也是自己定的。现在系统接管了,你的数据准度还重要吗?如果 Apple 航班服务的数据源和你自己的数据源产生了冲突——比如你的系统先拿到了登机口变更,但系统还没更新——用户在 Wallet 里看到的就是过时信息。
实际操作中,开发者应该保留自己的推送通道作为补充,而不是完全依赖系统。特别是涉及中国市场航班时,国内航班数据的实时性可能和 Apple 的数据源存在时差。originalDepartureDate 和 currentDepartureDate 的分离设计给了你这个空间——你可以继续更新 current* 字段,系统会自动处理展示。关键是不要因为”系统能自动更新”就彻底放弃自己的数据管道。
自动添加 API 的授权设计:一步到位还是温水煮青蛙?
PKPassLibrary.addPasses 的授权模型值得细看。用户首次调用时会弹出系统授权弹窗,授权后后续通行证可以自动添加。这个”一次授权,永久生效”的模型和通知权限的”每次询问”形成了鲜明对比——Apple 在通行证这个品类上选择了信任开发者。
但”自动”不等于”无感”。addPasses 的回调有三个状态:didAddPasses(成功)、didCancelAddPasses(用户取消)、shouldReviewPasses(需要用户审核)。第三个状态容易被忽略,它意味着系统认为这个通行证需要用户手动确认——可能是因为通行证类型不常见,或者用户之前撤销过授权。如果你的 UI 没处理这个分支,用户会看到一个没有反馈的黑洞。
调用时机也很关键。在用户刚完成购票、情绪高涨的时候弹授权,通过率远高于 App 启动时静默调用。这不是技术问题,是产品设计问题。
代码片段
一张票装下整个巡演
场景:演唱会巡演,一张通行证覆盖多个城市。
{
"upcomingPassInformation": [
{
"type": "event",
"identifier": "tour-2025-shanghai",
"displayName": "上海站",
"date": "2025-08-15T19:30:00+08:00",
"semantics": {
"venueName": "上海体育场",
"venuePlaceID": "ChIJ...",
"seats": [{ "seatSection": "A区", "seatRow": "5", "seatNumber": "12" }]
},
"isActive": true
},
{
"type": "event",
"identifier": "tour-2025-beijing",
"displayName": "北京站",
"date": "2025-08-22T19:30:00+08:00",
"semantics": {
"venueName": "国家体育场",
"venuePlaceID": "ChIJ..."
}
}
]
}
坑:identifier 必须全局唯一且稳定,通行证更新时如果 ID 变了,Wallet 会把它当成新票重新创建,用户会看到重复项。
购票后自动添加到 Wallet
场景:用户在 App 内完成购票,无需手动操作即添加到 Wallet。
import PassKit
func autoAddPass(_ passData: Data) {
let library = PKPassLibrary()
guard let pass = try? PKPass(data: passData) else { return }
library.addPasses([pass]) { result in
switch result {
case .didAddPasses:
// 添加成功
break
case .didCancelAddPasses:
// 用户取消,考虑降级到手动添加流程
showManualAddOption()
case .shouldReviewPasses:
// 系统要求用户审核,别忽略这个分支
showReviewPrompt()
@unknown default:
break
}
}
}
坑:shouldReviewPasses 经常被漏掉,但它的出现概率不低——尤其在用户之前拒绝过授权或通行证类型较新的情况下。
登机牌:把航班追踪交给系统
场景:创建支持自动航班追踪的登机牌。
{
"semantics": {
"airlineCode": "MU",
"flightNumber": "5101",
"originalDepartureDate": "2025-07-01T08:00:00+08:00",
"currentDepartureDate": "2025-07-01T08:30:00+08:00",
"departureAirportCode": "PVG",
"arrivalAirportCode": "PEK",
"departureGate": "C22"
}
}
坑:original* 和 current* 的差异是系统展示延误高亮的依据,但如果你只填了 original* 没填 current*,系统就无法判断是否有变更,航班追踪等于没开。
最佳实践
票务 App 开发者建议先把现有的多场次通行证数据模型梳理一遍,看看能不能用 upcomingPassInformation 替换掉当前”一场一票”的结构。迁移成本不高,但收益是实打实的——用户在 Wallet 里看到的不再是一堆散票,而是一张有组织的通行证。注意 identifier 的稳定性,这决定了迁移过程会不会产生重复票。
航空 App 开发者建议分两步走。第一步:确保登机牌的语义标签完整,特别是 airlineCode、flightNumber 和所有 current* 字段,这是启用系统级航班追踪的最低要求。第二步:不要砍掉自己的推送通道。Apple 航班服务是个加分项,不是替代品。在国内航线的数据时效性被验证之前,保持双通道是稳妥的选择。
自动添加 API 优先用在高频场景——月票、通勤卡、赛季套票。一次性票务(比如单场演唱会)的价值没那么大,因为用户下次购票可能是几个月后,授权状态已经”凉了”。调用时机卡在购票完成的那一刻,不要在 App 启动时静默调用。
还有什么值得关注
- Live Activity 支持通过信息分享航班状态,这对出行场景是社交属性的补完,但实现上依赖 Widget Extension,项目配置别忘了。
- 登机牌现在集成了 Maps 机场导航和 Find My 行李追踪,虽然不是开发者直接控制的功能,但如果你的登机牌语义标签够完整,这些系统能力会自动解锁。
upcomingPassInformation中的日期必须按时间顺序排列,否则 Wallet 界面会乱序展示——这个没有校验提示,出了 bug 很难排查。