$npx -y skills add dpearson2699/swift-ios-skills --skill shareplay-activitiesBuild shared real-time experiences using GroupActivities and SharePlay. Use when implementing shared media playback, collaborative app features, synchronized game state, or any FaceTime, Messages, AirDrop, or nearby visionOS group activity on iOS, macOS, tvOS, or visionOS.
| 1 | # GroupActivities / SharePlay |
| 2 | |
| 3 | Build shared real-time experiences using the GroupActivities framework. SharePlay |
| 4 | connects people over FaceTime, Messages, AirDrop, and nearby visionOS sharing, |
| 5 | synchronizing media playback, app state, or custom data. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Setup](#setup) |
| 10 | - [Defining a GroupActivity](#defining-a-groupactivity) |
| 11 | - [Session Lifecycle](#session-lifecycle) |
| 12 | - [Sending and Receiving Messages](#sending-and-receiving-messages) |
| 13 | - [Coordinated Media Playback](#coordinated-media-playback) |
| 14 | - [Starting SharePlay from Your App](#starting-shareplay-from-your-app) |
| 15 | - [GroupSessionJournal: File Transfer](#groupsessionjournal-file-transfer) |
| 16 | - [Common Mistakes](#common-mistakes) |
| 17 | - [Review Checklist](#review-checklist) |
| 18 | - [References](#references) |
| 19 | |
| 20 | ## Setup |
| 21 | |
| 22 | ### Capability |
| 23 | |
| 24 | Add the **Group Activities** capability to the app target in Xcode. Xcode adds |
| 25 | the required entitlement and updates the provisioning profile: |
| 26 | |
| 27 | ```xml |
| 28 | <key>com.apple.developer.group-session</key> |
| 29 | <true/> |
| 30 | ``` |
| 31 | |
| 32 | Configure this only for app targets. Group Activities are not available in |
| 33 | widgets, extensions, or App Clips. |
| 34 | |
| 35 | ### Checking Eligibility |
| 36 | |
| 37 | ```swift |
| 38 | import GroupActivities |
| 39 | |
| 40 | let observer = GroupStateObserver() |
| 41 | |
| 42 | // Check if a FaceTime call or Messages conversation is active |
| 43 | if observer.isEligibleForGroupSession { |
| 44 | showSharePlayButton() |
| 45 | } |
| 46 | ``` |
| 47 | |
| 48 | Observe changes reactively: |
| 49 | |
| 50 | ```swift |
| 51 | for await isEligible in observer.$isEligibleForGroupSession.values { |
| 52 | showSharePlayButton(isEligible) |
| 53 | } |
| 54 | ``` |
| 55 | |
| 56 | ## Defining a GroupActivity |
| 57 | |
| 58 | Conform to `GroupActivity` and provide metadata: |
| 59 | |
| 60 | ```swift |
| 61 | import GroupActivities |
| 62 | |
| 63 | struct WatchTogetherActivity: GroupActivity { |
| 64 | let movieID: String |
| 65 | let movieTitle: String |
| 66 | |
| 67 | var metadata: GroupActivityMetadata { |
| 68 | var meta = GroupActivityMetadata() |
| 69 | meta.title = movieTitle |
| 70 | meta.type = .watchTogether |
| 71 | meta.fallbackURL = URL(string: "https://example.com/movie/\(movieID)") |
| 72 | return meta |
| 73 | } |
| 74 | } |
| 75 | ``` |
| 76 | |
| 77 | ### Activity Types |
| 78 | |
| 79 | | Type | Use Case | |
| 80 | |---|---| |
| 81 | | `.generic` | Default for custom activities | |
| 82 | | `.watchTogether` | Video playback | |
| 83 | | `.listenTogether` | Audio playback | |
| 84 | | `.createTogether` | Collaborative creation (drawing, editing) | |
| 85 | | `.exploreTogether` | Shared browsing, planning, or exploration | |
| 86 | | `.learnTogether` | Shared learning or studying | |
| 87 | | `.readTogether` | Shared reading | |
| 88 | | `.shopTogether` | Shared shopping | |
| 89 | | `.workoutTogether` | Shared fitness sessions | |
| 90 | |
| 91 | `GroupActivity` is `Codable`; stored activity data must be codable. Add |
| 92 | `Transferable` only for SwiftUI `ShareLink`, SharePlay over AirDrop, or |
| 93 | AppKit/UIKit share sheets. Keep payloads minimal: use identifiers or URLs |
| 94 | instead of large data. |
| 95 | |
| 96 | ## Session Lifecycle |
| 97 | |
| 98 | ### Listening for Sessions |
| 99 | |
| 100 | Set up a long-lived task to receive sessions when another participant starts |
| 101 | the activity: |
| 102 | |
| 103 | ```swift |
| 104 | @Observable |
| 105 | @MainActor |
| 106 | final class SharePlayManager { |
| 107 | private var session: GroupSession<WatchTogetherActivity>? |
| 108 | private var messenger: GroupSessionMessenger? |
| 109 | private var sessionTasks: [Task<Void, Never>] = [] |
| 110 | |
| 111 | func observeSessions() { |
| 112 | Task { |
| 113 | for await session in WatchTogetherActivity.sessions() { |
| 114 | self.configureSession(session) |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | private func configureSession( |
| 120 | _ session: GroupSession<WatchTogetherActivity> |
| 121 | ) { |
| 122 | self.session = session |
| 123 | self.messenger = GroupSessionMessenger(session: session) |
| 124 | |
| 125 | // Observe session state changes |
| 126 | let stateTask = Task { |
| 127 | for await state in session.$state.values { |
| 128 | handleState(state) |
| 129 | } |
| 130 | } |
| 131 | sessionTasks.append(stateTask) |
| 132 | |
| 133 | // Observe participant changes |
| 134 | let participantTask = Task { |
| 135 | for await participants in session.$activeParticipants.values { |
| 136 | handleParticipants(participants) |
| 137 | } |
| 138 | } |
| 139 | sessionTasks.append(participantTask) |
| 140 | |
| 141 | // Join the session |
| 142 | session.join() |
| 143 | } |
| 144 | |
| 145 | private func cleanUp() { |
| 146 | sessionTasks.forEach { $0.cancel() } |
| 147 | sessionTasks.removeAll() |
| 148 | session = nil |
| 149 | messenger = nil |
| 150 | } |
| 151 | } |
| 152 | ``` |
| 153 | |
| 154 | ### Session States |
| 155 | |
| 156 | | State | Description | |
| 157 | |---|---| |
| 158 | | `.waiting` | Session exists but local participant has not joined | |
| 159 | | `.joined` | Local participant is actively in the session | |
| 160 | | `.invalidated(reason:)` | Session ended (check reason for details) | |
| 161 | |
| 162 | ### Handling State Changes |
| 163 | |
| 164 | ```swift |
| 165 | private func handleState(_ state: GroupSession<WatchTogetherActivity>.State) { |
| 166 | switch state { |
| 167 | case .waiting: |
| 168 | print("Waiting to join") |
| 169 | case .joined: |
| 170 | print("Joined session") |
| 171 | loadActiv |