WWDC Quick Look 💓 By SwiftGGTeam
What’s new in Wallet

What’s new in Wallet

观看原视频

Highlight

Wallet 在 2025 引入 upcomingPassInformation,让一张通行证能装下整个巡演或赛季的多个场次,每个场次有独立的 identifierisActive 状态、场馆语义和详情页。


核心内容

票务 App 的老问题是:一个赛季 20 场球赛,用户得在 Wallet 里堆 20 张票。月历视图里全是同一支球队的 logo,找哪一场全靠生日。去年 Wallet 推出的 Poster Event Ticket 改了视觉,但底层数据模型没动——每场仍是独立 pass。今年的 upcomingPassInformation 才是真正动了数据结构:一张 pass 里携带一个事件数组,每个事件有自己的 ID、日期、场馆、艺人、座位号和独立的事件指南。用户进入这张 pass 后看到的是一个”行程列表”,点进去才是单场详情。开发者迁移成本主要在两点:稳定的 identifier 用于增删事件,isActive 字段用于标记当前可入场的场次。

登机牌那边的改动更激进。Apple 直接把航班状态追踪接进了系统级的 Apple Flight Service:加票时 Wallet 用 airlineCodeflightNumberoriginalDepartureDate 三个语义字段去查找航班,之后登机口变更、延误、航站楼调整都由系统推送,不再依赖开发者后端的 APNs 轮询。登机时间也不需要单独维护——Wallet 在加票瞬间记下”起飞时间 - 登机时间”的差值,起飞时间被官方系统更新时,登机时间按这个差值自动平移。配合新的 Live Activity,用户在锁屏和灵动岛就能盯到落地,还能把这个 Live Activity 通过 Messages 分享给接机的人。

还有一个容易被忽略的改动:自动添加通行证。PKPassLibrary 新增了一次性授权能力 backgroundAddPasses,授权后调 addPasses(_:) 就能把 pass 直接塞进 Wallet,只用一条通知告知用户。对月票、通勤卡、频繁出差的航司 App 来说,省去了”打开 App → 找到订单 → 点添加到 Wallet”这条路径里至少两次点击。


详细内容

多场次票务的 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 必须在 pass 内全局唯一且稳定,后续要靠它在 pass update 中增删或更新这一场。
  • isActive 在场次开始时置 true、结束后置 false,控制这一场在 Wallet 列表里是否高亮为”即将到来”。
  • semantics 里的 venueName/venuePlaceID/venueLocation 直接驱动详情页顶部的场馆卡片和地图按钮,结构与单场 Poster Event Ticket 完全一致——但不会自动继承外层 pass 的同名字段,需要在每个事件里重复填。
  • images.headerImage 必须提供 1x/2x/3x 三种分辨率,因为 upcoming event 详情页同时在 iOS 和 watchOS 上显示。
  • 若希望本场复用外层 pass 的场馆地图,把 venueMap.reuseExisting 设为 true,否则 Wallet 不会显示场馆地图。

升级版登机牌的语义层09:00)。Wallet 用三个语义字段定位航班、用 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 是三件套,缺一项 Wallet 就无法在 Apple Flight Service 里找到航班,整个自动追踪降级为静态显示。
  • originalDepartureDate 永远是机票上写的那个时间,不要因为延误就去改它;延误请只更新 currentDepartureDate09:45)。
  • originalBoardingDateoriginalDepartureDate 在加票瞬间被用来计算”登机时长”,之后只要起飞时间被 Apple 推动,登机时间会按这个差值自动平移;想自己覆盖时用 currentBoardingDate
  • passengerServiceSSRs 接受 IATA 标准的 SSR 码(如 SVAN 服务犬、WCOB 机上轮椅),文本由 Wallet 渲染,跨航司展示一致。
  • airlineLoungePlaceID 配合 purchaseLoungeAccessURL 出现时,Wallet 在按钮下额外渲染地图预览(14:56);只给 URL 而不给 placeID,按钮会显得很孤立。

自动添加通行证 API17:35)。整套流程类似请求推送权限,但只能弹一次:

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,可在 .taskonAppear 里直接读。
  • addPasses(_:) 接受数组,一次能添加多张——很适合”买年票一次发 12 张月卡”这种场景。
  • 一旦授权,用户可在系统设置里随时关掉这个能力,App 内无法阻止,授权状态需要每次校验。
  • 后台添加成功后 Wallet 会发系统通知,不要再叠加自己的 in-app toast,避免重复打扰。

核心启发

  • 做什么:把现有的”一场一票”模型迁移到 upcomingPassInformation。 为什么值得做:用户钱包里散落的 20 张同一球队季票会被收成 1 张,详情页可以承载演出嘉宾、场馆地图、停车券等所有相关信息,提高单张 pass 的留存与触达率。 怎么开始:先在测试 pass 里加 2-3 个事件验证 Wallet 渲染效果,确认 identifier 在 pass update 中的增删行为符合预期,再批量改后端模板。

  • 做什么:为登机牌补齐语义层,启用 Apple Flight Service 追踪。 为什么值得做:可以下线自家”航班变更推送”那条链路(轮询 + APNs + 同步),并自动获得 Live Activity 和分享给亲友的能力,工程上是减法,体验上是加法。 怎么开始:第一步只补 airlineCodeflightNumberoriginalDepartureDatecurrentDepartureDate 四个字段;第二步再补 SSR 码和服务 URL;上线后用真实航班对照 Apple Flight Service 的数据准确度,国内航线建议先双通道并行一段时间再考虑下线自家推送。

  • 做什么:在购票成功的瞬间请求 backgroundAddPasses 授权。 为什么值得做:把”加入 Wallet”的成功率从两步交互的衰减率拉满,对月票、通勤卡、连锁酒店会员卡这种”用户预期就是要装进 Wallet”的场景收益最大。 怎么开始:先在购票完成页加一个明确的”自动加入 Wallet”开关,让用户主动触发授权请求;不要在 App 启动时静默调用,那是浪费唯一一次弹窗机会。

  • 做什么:用 passengerServiceSSRs 标准化乘机标签。 为什么值得做:服务犬、轮椅、特殊餐食这些信息走 IATA 标准码,跨航司在 Wallet 里展示一致,能减少地勤反复问询,也省去自己维护一套本地化字符串。 怎么开始:把后端订单系统里的特殊请求字段映射到 IATA SSR 码表,写入 pass.json 的 semantics.passengerServiceSSRs 数组即可。


关联 Session

评论

GitHub Issues · utterances