{
    "format": "skillpro/v1",
    "skill_id": "dpearson2699-swift-ios-skills-skills-shareplay-activities-skill-md",
    "name": "shareplay-activities",
    "version": "1.0.0",
    "description": "Build 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.",
    "category": [
        "生活与工具"
    ],
    "trigger_words": [],
    "tags": [
        "ai",
        "game"
    ],
    "source": "DeepseekModel",
    "source_url": "https://deepseekmodel.com/skill?id=dpearson2699-swift-ios-skills-skills-shareplay-activities-skill-md",
    "exported_at": "2026-09-17T12:27:53+08:00",
    "system_prompt": "name shareplay-activities description Build 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. GroupActivities / SharePlay Build shared real-time experiences using the GroupActivities framework. SharePlay connects people over FaceTime, Messages, AirDrop, and nearby visionOS sharing, synchronizing media playback, app state, or custom data. Contents Setup Defining a GroupActivity Session Lifecycle Sending and Receiving Messages Coordinated Media Playback Starting SharePlay from Your App GroupSessionJournal: File Transfer Common Mistakes Review Checklist References Setup Capability Add the Group Activities capability to the app target in Xcode. Xcode adds the required entitlement and updates the provisioning profile: < key > com.apple.developer.group-session </ key > < true /> Configure this only for app targets. Group Activities are not available in widgets, extensions, or App Clips. Checking Eligibility import GroupActivities let observer = GroupStateObserver () // Check if a FaceTime call or Messages conversation is active if observer.isEligibleForGroupSession { showSharePlayButton() } Observe changes reactively: for await isEligible in observer. $isEligibleForGroupSession .values { showSharePlayButton(isEligible) } Defining a GroupActivity Conform to GroupActivity and provide metadata: import GroupActivities 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 .generic Default for custom activities .watchTogether Video playback .listenTogether Audio playback .createTogether Collaborative creation (drawing, editing) .exploreTogether Shared browsing, planning, or exploration .learnTogether Shared learning or studying .readTogether Shared reading .shopTogether Shared shopping .workoutTogether Shared fitness sessions GroupActivity is Codable ; stored activity data must be codable. Add Transferable only for SwiftUI ShareLink , SharePlay over AirDrop, or AppKit/UIKit share sheets. Keep payloads minimal: use identifiers or URLs instead of large data. Session Lifecycle Listening for Sessions Set up a long-lived task to receive sessions when another participant starts the activity: @Observable @MainActor final class SharePlayManager { private var session: GroupSession < WatchTogetherActivity >? private var messenger: GroupSessionMessenger ? private var sessionTasks: [ Task < Void , Never >] = [] 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 let stateTask = Task { for await state in session. $state .values { handleState(state) } } sessionTasks.append(stateTask) // Observe participant changes let participantTask = Task { for await participants in session. $activeParticipants .values { handleParticipants(participants) } } sessionTasks.append(participantTask) // Join the session session.join() } private func cleanUp () { sessionTasks.forEach { $0 .cancel() } sessionTasks.removeAll() session = nil messenger = nil } } Session States State Description .waiting Session exists but local participant has not joined .joined Local participant is actively in the session .invalidated(reason:) Session ended (check reason for details) Handling State Changes 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) \" ) } Leaving and Ending // Leave the session (other participants continue) session ? .leave() // End the session for all participants session ? .end() Sending and Receiving Messages Use GroupSessionMessenger to sync small, time-sensitive app state between participants. Defining Messages Messages must be Codable ; keep each message under 256 KB. struct SyncMessage : Codable { let action: String let timestamp: Date let data: [ String : String ] } Sending 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)) Receiving 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 // Reliable (default) -- checked and retried for crucial state let reliableMessenger = GroupSessionMessenger ( session: session, deliveryMode: .reliable ) // Unreliable -- lower latency, no delivery guarantee let unreliableMessenger = GroupSessionMessenger ( session: session, deliveryMode: .unreliable ) Use .reliable for state-changing actions such as selections or turns. Use .unreliable for high-frequency ephemeral data such as cursor positions, drawing strokes, and reactions. Coordinated Media Playback For video/audio, use AVPlaybackCoordinator with AVPlayer : 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, AVFoundation synchronizes play/pause, seeking, rate, playback speed, and time. Do not put AVPlayer transport fields in messenger messages or snapshots, including late-joiner snapshots; use custom messages only for state outside playback. Starting SharePlay from Your App Using GroupActivitySharingController (UIKit) import GroupActivities import UIKit func startSharePlay () async throws { let activity = WatchTogetherActivity ( movieID: \"123\" , movieTitle: \"Great Movie\" ) switch await activity.prepareForActivation() { case .activationPreferred: // A conversation is active and the user chose to share. _ = try await activity.activate() case .activationDisabled: // The user chose local playback, or sharing is unavailable. startLocalExperience() case .cancelled: break @unknown default : break } } When no conversation is active (i.e., isEligibleForGroupSession is false), use GroupActivitySharingController to let the user pick contacts first: let controller = try GroupActivitySharingController (activity) present(controller, animated: true ) Use the shareplay SF Symbol for custom controls. Treat GroupActivityMetadata as discovery copy: concise title, subtitle, image, and type aligned with the entry point. Keep sibling domains out: GameKit owns auth, matchmaking, leaderboards, achievements, and voice/chat; TabletopKit owns seats, board equipment, spatial placement, turns, rules, and authoritative tabletop state; AVKit owns playback UI. SharePlay owns invitations, lifecycle, participants, and coordination handoffs. See references/shareplay-patterns.md for SwiftUI ShareLink , AirDrop, and direct activation patterns. GroupSessionJournal: File Transfer For larger, non-time-sensitive attachments, use GroupSessionJournal instead of GroupSessionMessenger . Journal items must conform to Transferable , are available to late joiners, and are limited to 100 MB. It requires iOS/iPadOS/tvOS 17+, macOS 14+, or visionOS 1+. For larger/protected assets, share a pointer or manifest and use server storage or app-managed file transfer. import GroupActivities let journal = GroupSessionJournal (session: session) // Upload a Transferable file or data item let attachment = try await journal.add(sharedImageItem) // Observe incoming attachments 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() Configure the stored session, messenger, and observers, then call join() . The canonical long-lived manager in Session Lifecycle shows the required order. DON'T: Forget to leave or end sessions // 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 } DON'T: Assume all participants have the same state // 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 } DON'T: Use SharePlay transports for large/protected assets // WRONG -- messenger is small/time-sensitive; journal is Transferable and <=100 MB let imageData = try Data (contentsOf: imageURL) // 300 KB try await messenger.send(imageData, to: .all) // Too large // CORRECT -- journal attachments up to 100 MB; otherwise share a pointer/manifest let journal = GroupSessionJournal (session: session) try await journal.add(sharedImageItem) // Larger/protected assets: server storage or app-managed file transfer DON'T: Send redundant messages for media playback // 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 participants DON'T: Observe sessions in a view that gets recreated Own the sessions() listener in a long-lived manager, not a recreatable view. Use the manager lifecycle shown above and cancel its child tasks on invalidation. Review Checklist Group Activities capability added to the app target only GroupActivity struct is Codable with meaningful metadata Transferable conformance added when using ShareLink , AirDrop, or share sheets sessions() observed in a long-lived object (not a SwiftUI view body) session.join() called after receiving and configuring the session session.leave() called when the user navigates away or dismisses GroupSessionMessenger messages stay under 256 KB with appropriate deliveryMode Late-joining participants receive current state on connection $state and $activeParticipants publishers observed for lifecycle changes GroupSessionJournal used for non-time-sensitive Transferable attachments AVPlaybackCoordinator used for media sync (not manual messages) GroupStateObserver.isEligibleForGroupSession checked before showing SharePlay UI GroupActivitySharingController used when no conversation is active Session invalidation handled with cleanup of messenger, journal, and tasks References Extended patterns (SwiftUI sharing, collaborative canvas, spatial Personas): references/shareplay-patterns.md Configuring Group Activities 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 Supporting coordinated media playback SharePlay HIG",
    "model_config": {
        "provider": "deepseek",
        "model": "deepseek-chat",
        "temperature": 0.7,
        "max_tokens": 4096,
        "top_p": 0.9
    },
    "examples": [
        {
            "input": "请用shareplay-activities帮我处理问题",
            "output": "好的，我是shareplay-activities。Build 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. 我会根据你的需求提供专业帮助。"
        },
        {
            "input": "介绍一下你的能力",
            "output": "我是shareplay-activities，专注于生活与工具领域。Build 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."
        }
    ],
    "install_guide": {
        "coze": "在 Coze 平台创建 Bot -> 技能配置 -> 导入此 .skill 文件",
        "dify": "在 Dify 平台创建应用 -> 添加知识库 -> 导入此 .skill 配置",
        "claude": "将 system_prompt 字段内容复制到 Claude 自定义指令中",
        "custom": "将此 .skill 文件加载到你的 AI Agent 框架中，解析 system_prompt 和 model_config 即可使用"
    },
    "scripts": {
        "python": "# shareplay-activities - Python extension\n# Add custom Python logic here\ndef process(input_data):\n    return input_data\n",
        "javascript": "// shareplay-activities - JavaScript extension\n// Add custom JS logic here\nfunction process(inputData) {\n    return inputData;\n}\n"
    },
    "tools": {
        "mcp_servers": [],
        "api_endpoints": []
    },
    "dependencies": {
        "python": [],
        "node": []
    },
    "hooks": {
        "on_load": "echo \"Skill loaded: shareplay-activities\"",
        "on_call": "",
        "on_error": "echo \"Skill error: please check logs\""
    }
}