View in English

  • Apple 开发者
    • 入门汇总

    探索“入门汇总”

    • 概览
    • 学习
    • Apple Developer Program

    及时了解最新动态

    • 最新动态
    • 开发者你好
    • 平台

    探索“平台”

    • Apple 平台
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    • App Store

    精选

    • 设计
    • 分发
    • 游戏
    • 配件
    • 网页
    • Home
    • CarPlay 车载
    • 技术

    探索“技术”

    • 概览
    • Xcode
    • Swift
    • SwiftUI

    精选

    • 辅助功能
    • App Intents
    • Apple 智能
    • 游戏
    • 机器学习与 AI
    • 安全性
    • Xcode Cloud
    • 社区

    探索“社区”

    • 概览
    • “与 Apple 会面交流”活动
    • 社区主导的活动
    • 开发者论坛
    • 开源

    精选

    • WWDC
    • Swift Student Challenge
    • 开发者故事
    • App Store 大奖
    • Apple 设计大奖
    • Apple Developer Centers
    • 文档

    探索“文档”

    • 文档库
    • 技术概述
    • 示例代码
    • 《人机界面指南》
    • 视频

    发布说明

    • 精选更新
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • Apple tvOS
    • Xcode
    • 下载

    探索“下载”

    • 所有下载
    • 操作系统
    • 应用程序
    • 设计资源

    精选

    • Xcode
    • TestFlight
    • 字体
    • SF Symbols
    • Icon Composer
    • 支持

    探索“支持”

    • 概览
    • 帮助指南
    • 开发者论坛
    • “反馈助理”
    • 联系我们

    精选

    • 《开发者账户帮助》
    • 《App 审核指南》
    • 《App Store Connect 帮助》
    • 即将实行的要求
    • 协议和准则
    • 系统状态
  • 快速链接

    • 活动
    • 新闻
    • 论坛
    • 示例代码
    • 视频
 

视频

打开菜单 关闭菜单
  • 专题
  • 所有视频
  • 关于

更多视频

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 打造实时沟通体验

    借助 LiveCommunicationKit,让你的实时通信 App 大变身,为用户带来深度整合的沟通体验。我们将介绍如何提供丰富的原生对话 UI,让你的 App 出现在恰如用户所需的地方,比如全屏显示在锁定屏幕上,以及通过灵动岛实现丝滑的多任务处理。与我们一起逐步了解如何整合这一消息收发和群组对话框架。

    章节

    • 0:01 - Introduction
    • 7:56 - Incoming conversations
    • 11:29 - Outgoing conversations
    • 13:18 - Groups

    资源

    • Initiating VoIP conversations with LiveCommunicationKit
    • Responding to VoIP Notifications from PushKit
    • LiveCommunicationKit
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • 了解 App Intents

    WWDC22

    • 利用 Push to Talk 优化语音通信
  • 搜索此视频…

    你好 我是Yaseen Apple软件工程师 本节课我将向你展示如何 提供丰富的原生会话界面 让你的App出现在 用户需要的地方 从锁定屏幕上的 全屏展示 到与Dynamic Island的 无缝多任务处理 这从会话接入的那一刻就开始了 当你的App采用此API时 其会话会在锁定屏幕上 以全屏方式呈现 包含联系人姓名、照片 以及一套标准操作控件 这与来电时的界面 完全一致

    采用此API的App 可获得相同的呈现效果 会话也可以出现在 电话App的最近通话中 以及联系人详情里 最近通话显示 通话对象和时间 用户可点击发起 新的会话 LiveCommunicationKit是 构建通信App的现代方式 可与这些系统体验 无缝集成 如果你的App使用了 传统实现方式 例如CXProvider API 现在正是迁移到 LiveCommunicationKit的好时机 它提供了更灵活 功能更丰富的API 以集成各种类型的 实时会话 让你的App全面支持 我将从核心概念讲起 什么是会话 会话的生命周期如何运作 以及你的App如何 与系统进行通信 接下来介绍接收会话的流程 从推送通知唤醒App 到在锁定屏幕上 呈现会话 然后介绍从App内 发起呼出会话 以及通过Siri 和最近通话访问会话 最后是群组会话 包括管理参与者 以及合并会话 为了了解这一切的工作原理 我将以一群大学好友为例 David Ryan Andre 以及Adam 他们使用我的语音会话App 规划年度聚会旅行 不过首先简要介绍一下 LiveCommunicationKit的工作原理 我刚才介绍的所有体验 都由一个对象驱动 那就是会话 会话代表人与人之间 的一次实时互动 它的存在时间取决于 是否有人在其中 所有人离开后 会话即结束 会话包含两个部分 句柄 代表会话中的参与者 以及能力 描述会话 可以执行的操作 我先从句柄讲起 句柄用于标识一个人 它有三个属性 kind、value和display name 我将逐一介绍 kind告诉系统 句柄的标识符类型 可以是电话号码、电子邮件地址 或通用字符串 如果你的App已通过 电话号码或邮件识别用户 设置正确的kind 可让系统匹配 将用户句柄与 已存联系人关联 并在系统会话界面中 显示其姓名和照片 value是标识符本身 即电话号码、电子邮件 或通用字符串 系统使用此值查找联系人 也是用户从最近通话回拨时 App收到的值 display name是系统 在无法匹配联系人时 所显示的名称 将其设置为App 已知的用户姓名 以便会话界面 始终有内容可显示 能力告知系统 会话可以执行的操作 系统据此决定 显示哪些控件 以及启用哪些手势 使会话界面只提供 App实际支持的功能

    系统显示一套标准的 会话内控件 静音、扬声器、键盘和更多 其中部分控件 需要App主动启用 此处声明了暂停能力 长按静音按钮即可 将会话置为保持状态 若未声明此能力 长按将不起任何作用 视频能力告知系统 这是一个视频会话 视频按钮由App的 provider配置启用 稍后我会介绍 能力可以在会话过程中 随时更改 当会话从音频升级到视频时 App更新相应能力 系统会立即反映这一变化 现在你已了解 会话的构成要素 我将介绍会话如何与系统 配合完成生命周期 当你的App首次向系统 报告会话时 会话从空闲状态开始 设备随即开始响铃 响铃期间 App可开始 本地初始化 当用户接听时 App将状态设置为joining 系统将界面更新为 显示会话正在连接 同时App完成设置 准备加入会话 此时尚未开始 音频或视频捕获 当App完成准备后 将状态设置为joined 并开始捕获和 发送音频与视频 会话随即正式开始 若用户切换到AirPods 或连接车载蓝牙 App将收到路由更改通知 并相应更新捕获管线 若你的App声明了 暂停能力 系统将启用其保持控件 当用户将会话置为保持时 系统会请求App暂停 App暂停媒体流 并报告新状态 当用户恢复会话时 系统要求App恢复 会话的媒体流 并报告此变化 会话结束时 你的App将状态设置为leaving 这一阶段App会 拆除会话 并清理其连接 拆除完成后 App将 状态设置为left 会话至此结束 那么你的App如何 实际驱动这一切 我先从架构讲起 你的App通过 ConversationManager驱动会话生命周期 以及其委托 你的App通过manager 报告会话和事件 每次向系统告知 新会话 或已有会话的变化 都需要通过manager进行 这样你的会话才能 出现在锁定屏幕上 在Dynamic Island中 以及其他所有地方 委托是你的App 响应系统的地方 每当会话需要 执行某项操作时 该操作会到达 委托方法 你的App执行相应工作 来完成它

    它们通过动作进行通信 当用户与系统界面交互时 例如从锁定屏幕接受会话 或从Dynamic Island结束会话 系统会创建一个动作 并发送给你的委托 当用户点击App 自身界面中的按钮时 则由你的App创建该动作 每一次交互 无论来自系统界面 还是App内部 都流经同一个委托回调 因此每个动作的逻辑 只需在一处处理 统一的代码路径意味着 不存在重复的状态管理 也不会有App与系统 状态不同步的风险 在App报告任何会话之前 需要先创建ConversationManager 下面是我的App 创建它的方式 ConversationManager的配置 告诉系统所需的一切 以便为你的App 呈现和管理会话 你可以在App生命周期的 任何时候更新此配置 此处我的App从Bundle中 提供了一个铃声 以及一个PNG图标 系统会同时展示两者 与我App的会话一同 显示在整个系统界面中 接下来是会话群组限制 当会话合并在一起时 即形成一个群组 这些值限制了 同时存在的群组数量 以及每个群组 可容纳的会话数量 还有我App的会话是否 显示在电话App的最近通话列表中 对于不支持回拨的 一次性房间 传入'false'将其排除在外 以及App是否支持视频 这将在系统界面中 启用视频按钮 以及我的App 支持哪些句柄类型 至此 我的App 创建了ConversationManager 由于整个App生命周期 都需要manager 我的App在启动时 就立即创建它 最后 我的App设置 manager的委托 为了在App退到后台 或设备锁定时继续会话 我的App注册了Audio和 Voice over IP后台模式 在Xcode中App目标的 能力设置里 配置好ConversationManager后 我将完整演示一次 传入会话的处理流程 David想开始规划 今年的聚会旅行 于是他与Adam发起会话 讨论潜在的目的地 Adam的设备已锁屏 但当David的会话接入时 它直接出现在锁定屏幕上 下面是我的App 实现这一效果的方式

    当David与Adam 发起会话时 他设备上的App构建了 一个包含两个字段的payload 一个代表David电话号码的句柄 以及该会话的 唯一标识符 David的App随后将payload 发送到我App的服务器 服务器再将其 转发到Adam的设备 当Adam的设备收到推送时 我的App唤醒并解码payload 然后使用解码出的句柄 构建Conversation.Update 此更新还包含 会话的能力 在本例中包括视频、暂停和合并 我的App随后使用该更新 向ConversationManager 报告会话 系统随即更新界面以匹配 PushKit负责在会话到来时 唤醒你的App 前提是App尚未运行 当你App的服务器 发送Voice over IP推送时 PushKit会启动你的App 并立即将payload 传递给委托方法 你的App必须在方法返回前 报告会话 否则系统 会终止该App 有关Voice over IP推送处理的更多内容 请查阅PushKit文档 以下是我App中 该PushKit委托方法 这是所有传入会话 经过的入口点 我的App首先从payload中 提取句柄和会话UUID 然后使用解码出的句柄 构建Conversation.Update 以及会话的能力 并进行报告

    Adam看到传入的会话 滑动接听 系统将界面更新为 显示会话正在连接 然后通过委托向我的App 发送JoinConversationAction 每次有人接听、暂停 或合并会话时 系统都会以动作的形式 传递给我的App 我的App在一处统一处理它们 即perform action委托回调 每次动作到来时 ConversationManager都会调用它 在回调内部 我的App使用 switch语句路由每种动作类型 到对应的处理器 我来追踪join动作

    处理join动作时 我的App首先验证 ConversationManager 是否正在跟踪与 该动作唯一标识符匹配的会话 如果未找到匹配的会话 我的App会使该动作失败 然后报告会话 已开始连接 这会将状态设置为joining 一旦我的App报告了连接事件 系统会在Adam的设备上 更新会话界面

    现在我的App进行自身设置 连接到服务器 并配置媒体流 这个工作是异步的 所以我的App将其封装在Task中 以保持委托响应畅通 设置完成后 我的App报告 连接成功并完成该动作 如果设置因任何原因失败 我的App会相应标记该动作 以便系统在其端 清理该会话 会话现在处于joined状态 界面也相应更新 权衡几个选项后 他们决定今年 前往冰岛 Adam点击了结束按钮 一旦他点击 会话 立即进入leaving状态 界面也随之更新 系统随后向我的App发送 一个EndConversationAction 经由委托 我的App拆除媒体流 并完成该动作 而在Adam的设备上 会话从系统界面 中消失 接下来我来谈谈 如何发起呼出会话 在David和Adam 决定前往冰岛后 Adam与Ryan发起会话 讨论大家的住宿安排 这次 Adam从 我的App内部发起会话 当有人从你的App内部 发起会话时 你应该将其报告给系统 以便人们在使用其他App时 仍可继续会话 为此 你的App创建一个 start动作来呼响接收方设备 然后在ConversationManager 上调用perform 并在其委托中处理该动作 方式与之前的 join动作相同 动作处理完成后 接收方要么接听 要么你的App报告 会话未被接听或失败

    下面是我的App表示 Adam与Ryan会话的方式 它构建了一个StartConversationAction 带有新生成的唯一标识符 和Ryan的句柄 然后将该动作发送给 ConversationManager manager首先更新系统界面 然后将动作转发 给委托 从这里开始 我的App的处理方式 与之前的join动作完全一致 会话连接成功后 Adam和Ryan浏览了几个选项 最终选定了 雷克雅未克附近的一间小屋 住宿确定后 他们又聊了几分钟 然后道别挂断了电话 会话结束后 用户可以从Spotlight 或最近通话中重新拨打 你的App通过支持 start call intent来处理这一点 该intent将以 NSUserActivity的形式 传递给你App的场景以继续处理 当你App的会话 保存到最近通话时 Apple Intelligence 已了解它们 但为了呈现你App对 会话的自有表示 也要在每次会话结束时 捐献你自己的intent 要了解有关在App中 集成intent的更多内容 请观看 "了解App Intents"专场 到目前为止所有内容 都是一对一的 群组会话 引入了多个参与者 在确定目的地和住宿后 Adam与David和Ryan 发起群组会话规划行程 群组会话跟踪 两种类型的成员 members包含所有 受邀加入会话的人 而activeRemoteMembers 只包含正在活跃传输媒体的人 系统两者都需要 members告诉系统 会话有多少参与者 activeRemoteMembers告诉系统 哪些人正在活跃发送媒体

    报告群组会话时 我的App为每位参与者 创建一个句柄 然后创建一个startAction 包含所有受邀成员并进行报告 会话开始后 David和Ryan相继加入 为了报告此变化 我的App构建一个会话更新 将Adam指定为localMember 声明新的activeRemoteMembership 并列出会话 支持的能力 包括合并和取消合并 我稍后会回到这个话题 我的App随后通过manager 报告会话更新 系统更新会话以 反映新的成员情况 在Adam、David和Ryan 讨论行程时 Ryan意识到还需要 确认Andre的行程是否合适 于是Ryan单独与Andre 发起会话来告知他 现在有两个会话 在并行运行 原来的群组 包含Adam、David和Ryan 以及Ryan与Andre 的单独会话 Ryan同时在两个会话中 但只在与Andre的会话中活跃 Andre和Ryan就行程日期 达成一致后 他们希望把所有人 召集回来一起确认完整计划 而不是让群组挂断 再重新发起会话 我的App可以将两者合并 我的App声明了合并能力 系统会启用 其合并控件界面 当Ryan点击它时 我的App合并两个会话 整个群组 现在加上Andre 可以完成行程规划 接下来我来讲解 会话合并的代码 取消合并遵循 相同的委托模式 所以一旦你看过了合并处理器 取消合并的处理将不会陌生 当两个会话合并时 ConversationManager会传递 一个MergeConversationAction 给我App的委托 merge动作携带 两个唯一标识符 分别对应两个待合并的会话 处理器使用这些标识符 查找我App本地表示 两个会话的对应数据 如果其中一个不存在 (可能已经结束) 处理器会立即 使该动作失败

    一旦我的App获得两个会话 它使用combineStreams 在服务器上合并媒体流 然后报告更新后的成员情况 并完成该动作 如果发生任何抛出 catch块会使该动作失败 一旦我的App合并了两个会话 系统会更新会话界面以 反映新的合并会话状态 所有人进入同一会话后 群组最终确定了行程安排 在蓝湖泡温泉 黄金圈游览一天 以及几次冰川徒步 行程确定后 Andre和Ryan想单独去 一起订购机票 这样他们可以 坐在飞机上相邻的位置 由于会话支持 取消合并能力 他们可以回到 各自的会话中 而Adam和David则处理 最后几个细节 这就是LiveCommunicationKit 从锁定屏幕上的 单个传入会话 到合并群组会话 该框架为你的App提供 系统级会话界面 出现在用户期望的每个地方 锁定屏幕上 最近通话中 以及Siri 以下是接下来的行动建议 采用ConversationManager 并在锁定屏幕上报告 你的第一个传入会话 捐献intent让Siri 知道如何发起会话 将所有临时令牌替换为 稳定句柄以支持从最近通话回拨 并确保及时更新 会话成员情况 感谢观看

    • 6:41 - Set up a conversation manager

      // Set up a conversation manager
      
      import LiveCommunicationKit
      
      let configuration = ConversationManager.Configuration(
        ringtoneName: "SampleRingtone.caf",
        iconTemplateImageData: UIImage(named: "SampleIcon")?.pngData(),
        maximumConversationGroups: 1,
        maximumConversationsPerConversationGroup: 2,
        includesConversationInRecents: true,
        supportsVideo: true,
        supportedHandleTypes: [.phoneNumber, .emailAddress]
      )
      
      let manager = ConversationManager(configuration: configuration)
      manager.delegate = self
    • 9:22 - Report the incoming conversation to the system

      // Report the incoming conversation to the system
      
      import LiveCommunicationKit
      import PushKit
      
      final class SamplePushHandler: NSObject, PKPushRegistryDelegate {
        func pushRegistry(
          _ registry: PKPushRegistry,
          didReceiveIncomingVoIPPushWith payload: PKPushPayload,
          metadata: PKVoIPPushMetadata) async {
      
          guard let (handle, uuid) = parseConversationPayload(from: payload) else { return }
      
          let capabilities = [.video, .pausing, .merging]
          let update = Conversation.Update(members: [handle], capabilities: capabilities)
          try? await manager.reportNewIncomingConversation(uuid: uuid, update: update)
        }
      }
    • 9:57 - Implement the delegate

      // Implement the delegate
      
      import LiveCommunicationKit
      
      final class SampleDelegate: ConversationManagerDelegate {
        func conversationManager(
          _ manager: ConversationManager,
          perform action: ConversationAction
        ) {
          switch action {
          case let action as JoinConversationAction:
            handleJoinAction(action)
          default:
            action.fail()
          }
        }
      }
    • 10:13 - Fulfill the join action

      // Handle a failed connection
      
      extension SampleDelegate {
       func handleJoinAction(_ action: JoinConversationAction) {
          guard let conversation = manager.conversations.first(where: {$0.uuid == uuid })else {
            return action.fail()
          }
      
          manager.reportConversationEvent(.conversationStartedConnecting(.now), for: conversation)
      
          Task {
            do {
              try await setupMediaStream(with: action.conversationUUID)
              manager.reportConversationEvent(.conversationConnected(.now), for: conversation)
              action.fulfill(dateConnected: .now)
            } catch {
              action.fail()
            }
          }
        }
      }
    • 11:17 - Route end actions

      // Route end actions
      
      final class SampleDelegate: ConversationManagerDelegate {
        // …
        func conversationManager(
          _ manager: ConversationManager,
          perform action: ConversationAction
        ) {
          switch action {
          case let action as JoinConversationAction:
            handleJoinAction(action)
          case let action as EndConversationAction:
            handleEndAction(action)
          default:
            action.fail()
          }
        }
      }
    • 12:14 - Create a start action

      let startAction = StartConversationAction(
        conversationUUID: UUID(),
        handles: [Handle(type: .phoneNumber, value: "+1-650-555-0199", displayName: "Ryan Notch")],
        isVideo: false
      )
    • 12:23 - Perform the action

      try await manager.perform([startAction])
    • 12:29 - Route start actions

      // Route start actions
      
      final class SampleDelegate: ConversationManagerDelegate {
        // …
        func conversationManager(
          _ manager: ConversationManager,
          perform action: ConversationAction
        ) {
          switch action {
          case let action as JoinConversationAction:
            handleJoinAction(action)
          case let action as EndConversationAction:
            handleEndAction(action)
          case let action as StartConversationAction:
            handleStartAction(action)
          default:
            action.fail()
          }
        }
      }
    • 13:51 - Start group conversations

      // Start group conversations
      
      let adam = Handle(type: .emailAddress,
                        value: "adam.halwani@icloud.com",
                        displayName: "Adam Halwani")
      let david = Handle(type: .emailAddress,
                         value: "david@example.com",
                         displayName: "David Evans")
      let ryan = Handle(type: .phoneNumber,
                        value: "+16505550199",
                        displayName: "Ryan Notch")
      
      let startAction = StartConversationAction(
        conversationUUID: UUID(),
        handles: [david, ryan],
        isVideo: false
      )
      try await manager.perform([startAction])
    • 14:01 - Report group membership updates

      // Report group membership updates
      
      let update = Conversation.Update(
        localMember: adam,
        members: [david, ryan],
        activeRemoteMembers: [david, ryan],
        capabilities: [.merging, .pausing, .unmerging]
      )
      
      manager.reportConversationEvent(
        .conversationUpdated(update),
        for: conversation
      )
    • 15:26 - Route merge actions

      // Route merge actions
      
      final class SampleDelegate: ConversationManagerDelegate {
        func conversationManager(
          _ manager: ConversationManager,
          perform action: ConversationAction
        ) {
          switch action {
          case let action as JoinConversationAction:
            handleJoinAction(action)
          case let action as EndConversationAction:
            handleEndAction(action)
          case let action as StartConversationAction:
              handleStartAction(action)
          case let action as MergeConversationAction:
            handleMergeAction(action)
          default:
            action.fail()
          }
        }
      }
    • 15:33 - Handle the merge action

      // Handle the merge action
      
      extension SampleDelegate {
        func handleMergeAction(_ action: MergeConversationAction) {
          let sourceUUID = action.conversationUUID
          let targetUUID = action.conversationUUIDToMergeWith
          guard manager.conversations.contains(where: { $0.uuid == sourceUUID }),
                manager.conversations.contains(where: { $0.uuid == targetUUID }) else {
            return action.fail()
          }
      
          Task {
            do {
              let update = try await combineStreams(from: sourceUUID, into: targetUUID)
              manager.reportConversationEvent(.conversationUpdated(update), for: target)
              action.fulfill()
            } catch {
              action.fail()
            }
          }
        }
      }
    • 0:01 - Introduction
    • LiveCommunicationKit is the modern way to build live conversation experiences that integrate with the system. Conversations in LiveCommunicationKit are built upon fundamental elements, such as handles, display names, and capabilities, that configure the interface, as well as a single ConversationManager object to manage the full lifecycle.

    • 7:56 - Incoming conversations
    • Incoming conversations rely on the ConversationManager class where you configure properties like ringtones, group limits, and supported handles. Use PushKit to deliver an incoming conversation to a device, then report it to the ConversationManager.

    • 11:29 - Outgoing conversations
    • Start outgoing conversations initiated within the app by performing a StartConversationAction. This allows your app's ConversationManager delegate to handle the entire process, using the same unified action-handling logic for actions started by in-app or system UI.

    • 13:18 - Groups
    • Track the full list of invited members in addition to the currently active remote members so the interface stays perfectly in sync as people join or drop off. Advanced call management is handled through delegate actions and supports merging and unmerging calls as needed.

Developer Footer

  • 视频
  • WWDC26
  • 打造实时沟通体验
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • Apple 智能
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • Mini Apps Partner Program
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    阅读最近新闻。
    获取 Apple Developer App。
    版权所有 © 2026 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则