shareplay-activities
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGroupActivities / SharePlay
GroupActivities / SharePlay
Build shared real-time experiences using the GroupActivities framework. SharePlay
connects people over FaceTime or iMessage, synchronizing media playback, app state,
or custom data. Targets Swift 6.2 / iOS 26+.
使用GroupActivities框架构建共享实时体验。SharePlay通过FaceTime或iMessage连接用户,同步媒体播放、应用状态或自定义数据。目标环境为Swift 6.2 / iOS 26+。
Contents
目录
Setup
配置步骤
Entitlements
权限配置
Add the Group Activities entitlement to your app:
xml
<key>com.apple.developer.group-session</key>
<true/>为你的应用添加Group Activities权限:
xml
<key>com.apple.developer.group-session</key>
<true/>Info.plist
Info.plist配置
For apps that start SharePlay without a FaceTime call (iOS 17+), add:
xml
<key>NSSupportsGroupActivities</key>
<true/>对于无需FaceTime通话即可启动SharePlay的应用(iOS 17+),添加:
xml
<key>NSSupportsGroupActivities</key>
<true/>Checking Eligibility
检查可用性
swift
import GroupActivities
let observer = GroupStateObserver()
// Check if a FaceTime call or iMessage group is active
if observer.isEligibleForGroupSession {
showSharePlayButton()
}Observe changes reactively:
swift
for await isEligible in observer.$isEligibleForGroupSession.values {
showSharePlayButton(isEligible)
}swift
import GroupActivities
let observer = GroupStateObserver()
// 检查是否存在活跃的FaceTime通话或iMessage群组
if observer.isEligibleForGroupSession {
showSharePlayButton()
}响应式监听状态变化:
swift
for await isEligible in observer.$isEligibleForGroupSession.values {
showSharePlayButton(isEligible)
}Defining a GroupActivity
定义GroupActivity
Conform to and provide metadata:
GroupActivityswift
import GroupActivities
import CoreTransferable
struct WatchTogetherActivity: GroupActivity {
let movieID: String
let movieTitle: String
var metadata: GroupActivityMetadata {
var meta = GroupActivityMetadata()
meta.title = movieTitle
meta.type = .watchTogether
meta.fallbackURL = URL(string: "https://example.com/movie/\(movieID)")
return meta
}
}遵循协议并提供元数据:
GroupActivityswift
import GroupActivities
import CoreTransferable
struct WatchTogetherActivity: GroupActivity {
let movieID: String
let movieTitle: String
var metadata: GroupActivityMetadata {
var meta = GroupActivityMetadata()
meta.title = movieTitle
meta.type = .watchTogether
meta.fallbackURL = URL(string: "https://example.com/movie/\(movieID)")
return meta
}
}Activity Types
活动类型
| Type | Use Case |
|---|---|
| Default for custom activities |
| Video playback |
| Audio playback |
| Collaborative creation (drawing, editing) |
| Shared fitness sessions |
The activity struct must conform to so the system can transfer it
between devices.
Codable| 类型 | 适用场景 |
|---|---|
| 自定义活动默认类型 |
| 视频播放 |
| 音频播放 |
| 协作创作(绘图、编辑) |
| 共享健身会话 |
活动结构体必须遵循协议,以便系统在设备间传输数据。
CodableSession Lifecycle
会话生命周期
Listening for Sessions
监听会话
Set up a long-lived task to receive sessions when another participant starts
the activity:
swift
@Observable
@MainActor
final class SharePlayManager {
private var session: GroupSession<WatchTogetherActivity>?
private var messenger: GroupSessionMessenger?
private var tasks = TaskGroup()
func observeSessions() {
Task {
for await session in WatchTogetherActivity.sessions() {
self.configureSession(session)
}
}
}
private func configureSession(
_ session: GroupSession<WatchTogetherActivity>
) {
self.session = session
self.messenger = GroupSessionMessenger(session: session)
// Observe session state changes
Task {
for await state in session.$state.values {
handleState(state)
}
}
// Observe participant changes
Task {
for await participants in session.$activeParticipants.values {
handleParticipants(participants)
}
}
// Join the session
session.join()
}
}设置长生命周期任务,接收其他参与者启动的活动会话:
swift
@Observable
@MainActor
final class SharePlayManager {
private var session: GroupSession<WatchTogetherActivity>?
private var messenger: GroupSessionMessenger?
private var tasks = TaskGroup()
func observeSessions() {
Task {
for await session in WatchTogetherActivity.sessions() {
self.configureSession(session)
}
}
}
private func configureSession(
_ session: GroupSession<WatchTogetherActivity>
) {
self.session = session
self.messenger = GroupSessionMessenger(session: session)
// 监听会话状态变化
Task {
for await state in session.$state.values {
handleState(state)
}
}
// 监听参与者变化
Task {
for await participants in session.$activeParticipants.values {
handleParticipants(participants)
}
}
// 加入会话
session.join()
}
}Session States
会话状态
| State | Description |
|---|---|
| Session exists but local participant has not joined |
| Local participant is actively in the session |
| Session ended (check reason for details) |
| 状态 | 描述 |
|---|---|
| 会话已存在,但本地参与者尚未加入 |
| 本地参与者已活跃加入会话 |
| 会话已结束(查看reason获取详情) |
Handling State Changes
处理状态变化
swift
private func handleState(_ state: GroupSession<WatchTogetherActivity>.State) {
switch state {
case .waiting:
print("Waiting to join")
case .joined:
print("Joined session")
loadActivity(session?.activity)
case .invalidated(let reason):
print("Session ended: \(reason)")
cleanUp()
@unknown default:
break
}
}
private func handleParticipants(_ participants: Set<Participant>) {
print("Active participants: \(participants.count)")
}swift
private func handleState(_ state: GroupSession<WatchTogetherActivity>.State) {
switch state {
case .waiting:
print("等待加入会话")
case .joined:
print("已加入会话")
loadActivity(session?.activity)
case .invalidated(let reason):
print("会话结束:\(reason)")
cleanUp()
@unknown default:
break
}
}
private func handleParticipants(_ participants: Set<Participant>) {
print("活跃参与者数量:\(participants.count)")
}Leaving and Ending
离开与结束会话
swift
// Leave the session (other participants continue)
session?.leave()
// End the session for all participants
session?.end()swift
// 离开会话(其他参与者可继续)
session?.leave()
// 结束所有参与者的会话
session?.end()Sending and Receiving Messages
消息收发
Use to sync app state between participants.
GroupSessionMessenger使用在参与者之间同步应用状态。
GroupSessionMessengerDefining Messages
定义消息
Messages must be :
Codableswift
struct SyncMessage: Codable {
let action: String
let timestamp: Date
let data: [String: String]
}消息必须遵循协议:
Codableswift
struct SyncMessage: Codable {
let action: String
let timestamp: Date
let data: [String: String]
}Sending
发送消息
swift
func sendSync(_ message: SyncMessage) async throws {
guard let messenger else { return }
try await messenger.send(message, to: .all)
}
// Send to specific participants
try await messenger.send(message, to: .only(participant))swift
func sendSync(_ message: SyncMessage) async throws {
guard let messenger else { return }
try await messenger.send(message, to: .all)
}
// 发送给特定参与者
try await messenger.send(message, to: .only(participant))Receiving
接收消息
swift
func observeMessages() {
guard let messenger else { return }
Task {
for await (message, context) in messenger.messages(of: SyncMessage.self) {
let sender = context.source
handleReceivedMessage(message, from: sender)
}
}
}swift
func observeMessages() {
guard let messenger else { return }
Task {
for await (message, context) in messenger.messages(of: SyncMessage.self) {
let sender = context.source
handleReceivedMessage(message, from: sender)
}
}
}Delivery Modes
传输模式
swift
// Reliable (default) -- guaranteed delivery, ordered
let reliableMessenger = GroupSessionMessenger(
session: session,
deliveryMode: .reliable
)
// Unreliable -- faster, no guarantees (good for frequent position updates)
let unreliableMessenger = GroupSessionMessenger(
session: session,
deliveryMode: .unreliable
)Use for state-changing actions (play/pause, selections). Use
for high-frequency ephemeral data (cursor positions, drawing strokes).
.reliable.unreliableswift
// 可靠模式(默认)-- 保证传输且有序
let reliableMessenger = GroupSessionMessenger(
session: session,
deliveryMode: .reliable
)
// 不可靠模式 -- 速度更快,无保障(适合频繁的位置更新)
let unreliableMessenger = GroupSessionMessenger(
session: session,
deliveryMode: .unreliable
)状态变更操作(播放/暂停、选择)使用模式。高频临时数据(光标位置、绘图笔触)使用模式。
.reliable.unreliableCoordinated Media Playback
协调媒体播放
For video/audio, use with :
AVPlaybackCoordinatorAVPlayerswift
import AVFoundation
import GroupActivities
func configurePlayback(
session: GroupSession<WatchTogetherActivity>,
player: AVPlayer
) {
// Connect the player's coordinator to the session
let coordinator = player.playbackCoordinator
coordinator.coordinateWithSession(session)
}Once connected, play/pause/seek actions on any participant's player are
automatically synchronized to all other participants. No manual message
passing is needed for playback controls.
对于视频/音频,将与配合使用:
AVPlaybackCoordinatorAVPlayerswift
import AVFoundation
import GroupActivities
func configurePlayback(
session: GroupSession<WatchTogetherActivity>,
player: AVPlayer
) {
// 将播放器的协调器与会话关联
let coordinator = player.playbackCoordinator
coordinator.coordinateWithSession(session)
}关联后,任何参与者播放器上的播放/暂停/跳转操作都会自动同步到所有其他参与者,无需手动传递消息实现播放控制。
Handling Playback Events
处理播放事件
swift
// Notify participants about playback events
let event = GroupSessionEvent(
originator: session.localParticipant,
action: .play,
url: nil
)
session.showNotice(event)swift
// 通知参与者播放事件
let event = GroupSessionEvent(
originator: session.localParticipant,
action: .play,
url: nil
)
session.showNotice(event)Starting SharePlay from Your App
从应用内启动SharePlay
Using GroupActivitySharingController (UIKit)
使用GroupActivitySharingController(UIKit)
swift
import GroupActivities
import UIKit
func startSharePlay() async throws {
let activity = WatchTogetherActivity(
movieID: "123",
movieTitle: "Great Movie"
)
switch await activity.prepareForActivation() {
case .activationPreferred:
// Present the sharing controller
let controller = try GroupActivitySharingController(activity)
present(controller, animated: true)
case .activationDisabled:
// SharePlay is disabled or unavailable
print("SharePlay not available")
case .cancelled:
break
@unknown default:
break
}
}For (SwiftUI) and direct patterns, see
.
ShareLinkactivity.activate()references/shareplay-patterns.mdswift
import GroupActivities
import UIKit
func startSharePlay() async throws {
let activity = WatchTogetherActivity(
movieID: "123",
movieTitle: "精彩电影"
)
switch await activity.prepareForActivation() {
case .activationPreferred:
// 展示共享控制器
let controller = try GroupActivitySharingController(activity)
present(controller, animated: true)
case .activationDisabled:
// SharePlay不可用或已禁用
print("SharePlay不可用")
case .cancelled:
break
@unknown default:
break
}
}关于(SwiftUI)和直接调用的模式,请查看。
ShareLinkactivity.activate()references/shareplay-patterns.mdGroupSessionJournal: File Transfer
GroupSessionJournal:文件传输
For large data (images, files), use instead of
(which has a size limit):
GroupSessionJournalGroupSessionMessengerswift
import GroupActivities
let journal = GroupSessionJournal(session: session)
// Upload a file
let attachment = try await journal.add(imageData)
// Observe incoming attachments
Task {
for await attachments in journal.attachments {
for attachment in attachments {
let data = try await attachment.load(Data.self)
handleReceivedFile(data)
}
}
}对于大型数据(图片、文件),请使用而非(后者有单条消息大小限制):
GroupSessionJournalGroupSessionMessengerswift
import GroupActivities
let journal = GroupSessionJournal(session: session)
// 上传文件
let attachment = try await journal.add(imageData)
// 监听传入的附件
Task {
for await attachments in journal.attachments {
for attachment in attachments {
let data = try await attachment.load(Data.self)
handleReceivedFile(data)
}
}
}Common Mistakes
常见错误
DON'T: Forget to call session.join()
错误做法:忘记调用session.join()
swift
// WRONG -- session is received but never joined
for await session in MyActivity.sessions() {
self.session = session
// Session stays in .waiting state forever
}
// CORRECT -- join after configuring
for await session in MyActivity.sessions() {
self.session = session
self.messenger = GroupSessionMessenger(session: session)
session.join()
}swift
// 错误 -- 接收会话但从未加入
for await session in MyActivity.sessions() {
self.session = session
// 会话将一直处于.waiting状态
}
// 正确 -- 配置后加入会话
for await session in MyActivity.sessions() {
self.session = session
self.messenger = GroupSessionMessenger(session: session)
session.join()
}DON'T: Forget to leave or end sessions
错误做法:忘记离开或结束会话
swift
// WRONG -- session stays alive after the user navigates away
func viewDidDisappear() {
// Nothing -- session leaks
}
// CORRECT -- leave when the view is dismissed
func viewDidDisappear() {
session?.leave()
session = nil
messenger = nil
}swift
// 错误 -- 用户离开后会话仍保持活跃
func viewDidDisappear() {
// 无操作 -- 会话出现泄漏
}
// 正确 -- 视图消失时离开会话
func viewDidDisappear() {
session?.leave()
session = nil
messenger = nil
}DON'T: Assume all participants have the same state
错误做法:假设所有参与者状态一致
swift
// WRONG -- broadcasting state without handling late joiners
func onJoin() {
// New participant has no idea what the current state is
}
// CORRECT -- send full state to new participants
func handleParticipants(_ participants: Set<Participant>) {
let newParticipants = participants.subtracting(knownParticipants)
for participant in newParticipants {
Task {
try await messenger?.send(currentState, to: .only(participant))
}
}
knownParticipants = participants
}swift
// 错误 -- 广播状态但未处理迟加入的参与者
func onJoin() {
// 新参与者无法获知当前状态
}
// 正确 -- 向新参与者发送完整当前状态
func handleParticipants(_ participants: Set<Participant>) {
let newParticipants = participants.subtracting(knownParticipants)
for participant in newParticipants {
Task {
try await messenger?.send(currentState, to: .only(participant))
}
}
knownParticipants = participants
}DON'T: Use GroupSessionMessenger for large data
错误做法:使用GroupSessionMessenger传输大型数据
swift
// WRONG -- messenger has a per-message size limit
let largeImage = try Data(contentsOf: imageURL) // 5 MB
try await messenger.send(largeImage, to: .all) // May fail
// CORRECT -- use GroupSessionJournal for files
let journal = GroupSessionJournal(session: session)
try await journal.add(largeImage)swift
// 错误 -- messenger有单条消息大小限制
let largeImage = try Data(contentsOf: imageURL) // 5 MB
try await messenger.send(largeImage, to: .all) // 可能失败
// 正确 -- 使用GroupSessionJournal传输文件
let journal = GroupSessionJournal(session: session)
try await journal.add(largeImage)DON'T: Send redundant messages for media playback
错误做法:手动同步媒体播放消息
swift
// WRONG -- manually syncing play/pause when using AVPlayer
func play() {
player.play()
try await messenger.send(PlayMessage(), to: .all)
}
// CORRECT -- let AVPlaybackCoordinator handle it
player.playbackCoordinator.coordinateWithSession(session)
player.play() // Automatically synced to all participantsswift
// 错误 -- 使用AVPlayer时手动同步播放/暂停
func play() {
player.play()
try await messenger.send(PlayMessage(), to: .all)
}
// 正确 -- 让AVPlaybackCoordinator处理同步
player.playbackCoordinator.coordinateWithSession(session)
player.play() // 自动同步到所有参与者DON'T: Observe sessions in a view that gets recreated
错误做法:在会重建的视图中监听会话
swift
// WRONG -- each time the view appears, a new listener is created
struct MyView: View {
var body: some View {
Text("Hello")
.task {
for await session in MyActivity.sessions() { }
}
}
}
// CORRECT -- observe sessions in a long-lived manager
@Observable
final class ActivityManager {
init() {
Task {
for await session in MyActivity.sessions() {
configureSession(session)
}
}
}
}swift
// 错误 -- 每次视图出现时都会创建新的监听器
struct MyView: View {
var body: some View {
Text("Hello")
.task {
for await session in MyActivity.sessions() { }
}
}
}
// 正确 -- 在长生命周期的管理器中监听会话
@Observable
final class ActivityManager {
init() {
Task {
for await session in MyActivity.sessions() {
configureSession(session)
}
}
}
}Review Checklist
审核检查清单
- Group Activities entitlement () added
com.apple.developer.group-session - struct is
GroupActivitywith meaningful metadataCodable - observed in a long-lived object (not a SwiftUI view body)
sessions() - called after receiving and configuring the session
session.join() - called when the user navigates away or dismisses
session.leave() - created with appropriate
GroupSessionMessengerdeliveryMode - Late-joining participants receive current state on connection
- and
$statepublishers observed for lifecycle changes$activeParticipants - used for large file transfers instead of messenger
GroupSessionJournal - used for media sync (not manual messages)
AVPlaybackCoordinator - checked before showing SharePlay UI
GroupStateObserver.isEligibleForGroupSession - called before presenting sharing controller
prepareForActivation() - Session invalidation handled with cleanup of messenger, journal, and tasks
- 已添加Group Activities权限()
com.apple.developer.group-session - 结构体遵循
GroupActivity协议并包含有意义的元数据Codable - 在长生命周期对象中监听(而非SwiftUI视图体)
sessions() - 接收并配置会话后调用了
session.join() - 用户离开或关闭界面时调用了
session.leave() - 为配置了合适的
GroupSessionMessengerdeliveryMode - 迟加入的参与者在连接时能收到当前状态
- 监听了和
$state发布者以处理生命周期变化$activeParticipants - 大型文件传输使用而非messenger
GroupSessionJournal - 媒体同步使用(而非手动消息)
AVPlaybackCoordinator - 展示SharePlay UI前检查了
GroupStateObserver.isEligibleForGroupSession - 展示共享控制器前调用了
prepareForActivation() - 会话失效时清理了messenger、journal和任务
References
参考资料
- Extended patterns (collaborative canvas, spatial Personas, custom templates):
references/shareplay-patterns.md - GroupActivities framework
- GroupActivity protocol
- GroupSession
- GroupSessionMessenger
- GroupSessionJournal
- GroupStateObserver
- GroupActivitySharingController
- Defining your app's SharePlay activities
- Presenting SharePlay activities from your app's UI
- Synchronizing data during a SharePlay activity
- 扩展模式(协作画布、空间角色、自定义模板):
references/shareplay-patterns.md - GroupActivities框架
- GroupActivity协议
- GroupSession
- GroupSessionMessenger
- GroupSessionJournal
- GroupStateObserver
- GroupActivitySharingController
- 定义应用的SharePlay活动
- 从应用UI中启动SharePlay活动
- SharePlay活动期间同步数据