Claude Code Plugins

Community-maintained marketplace

Feedback
27
0

Transferable protocol for drag and drop, copy/paste, ShareLink, and data transfer. Use when user asks about sharing, drag and drop, copy paste, ShareLink, Transferable, or clipboard operations.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name transferable-sharing
description Transferable protocol for drag and drop, copy/paste, ShareLink, and data transfer. Use when user asks about sharing, drag and drop, copy paste, ShareLink, Transferable, or clipboard operations.
allowed-tools Bash, Read, Write, Edit

Transferable and Sharing

Comprehensive guide to the Transferable protocol for drag and drop, copy/paste, ShareLink, and cross-app data transfer in SwiftUI.

Prerequisites

  • iOS 16+ for Transferable (iOS 26 recommended)
  • Xcode 26+

Transferable Protocol

Overview

Transferable is Swift's declarative way to make types shareable across apps and system features:

  • Drag and drop
  • Copy/paste
  • Share sheets
  • Universal clipboard

Built-in Conformances

// Already Transferable
String.self
Data.self
URL.self
Image.self
AttributedString.self

Basic Conformance

import CoreTransferable

struct Note: Transferable {
    var id: UUID
    var title: String
    var content: String

    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .note)
    }
}

// Define custom UTType
import UniformTypeIdentifiers

extension UTType {
    static var note: UTType {
        UTType(exportedAs: "com.yourapp.note")
    }
}

Info.plist UTType Declaration

<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.data</string>
            <string>public.content</string>
        </array>
        <key>UTTypeDescription</key>
        <string>Note Document</string>
        <key>UTTypeIdentifier</key>
        <string>com.yourapp.note</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <array>
                <string>note</string>
            </array>
        </dict>
    </dict>
</array>

Transfer Representations

CodableRepresentation

For Codable types:

struct Task: Codable, Transferable {
    var id: UUID
    var title: String
    var isComplete: Bool

    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .task)
    }
}

DataRepresentation

For raw data conversion:

struct ImageItem: Transferable {
    var imageData: Data

    static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(contentType: .png) { item in
            item.imageData
        } importing: { data in
            ImageItem(imageData: data)
        }
    }
}

FileRepresentation

For file-based transfer:

struct Document: Transferable {
    var url: URL

    static var transferRepresentation: some TransferRepresentation {
        FileRepresentation(contentType: .pdf) { document in
            SentTransferredFile(document.url)
        } importing: { received in
            let destination = FileManager.default.temporaryDirectory
                .appendingPathComponent(received.file.lastPathComponent)
            try FileManager.default.copyItem(at: received.file, to: destination)
            return Document(url: destination)
        }
    }
}

ProxyRepresentation

Export as another type:

struct Note: Transferable {
    var title: String
    var content: String

    static var transferRepresentation: some TransferRepresentation {
        // Primary: full note data
        CodableRepresentation(contentType: .note)

        // Fallback: plain text
        ProxyRepresentation(exporting: \.plainText)
    }

    var plainText: String {
        "\(title)\n\n\(content)"
    }
}

Multiple Representations

Order Matters

struct RichContent: Transferable {
    var title: String
    var htmlContent: String
    var plainContent: String

    static var transferRepresentation: some TransferRepresentation {
        // Most specific first
        CodableRepresentation(contentType: .richContent)

        // HTML fallback
        DataRepresentation(contentType: .html) { content in
            content.htmlContent.data(using: .utf8)!
        } importing: { data in
            RichContent(
                title: "Imported",
                htmlContent: String(data: data, encoding: .utf8) ?? "",
                plainContent: ""
            )
        }

        // Plain text fallback
        ProxyRepresentation(exporting: \.plainContent)
    }
}

Conditional Representations

struct MediaItem: Transferable {
    var imageData: Data?
    var videoURL: URL?

    static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(contentType: .png) { item in
            guard let data = item.imageData else {
                throw TransferError.noImageData
            }
            return data
        } importing: { data in
            MediaItem(imageData: data, videoURL: nil)
        }

        FileRepresentation(contentType: .movie) { item in
            guard let url = item.videoURL else {
                throw TransferError.noVideoURL
            }
            return SentTransferredFile(url)
        } importing: { received in
            MediaItem(imageData: nil, videoURL: received.file)
        }
    }
}

ShareLink

Basic ShareLink

struct ContentView: View {
    let note: Note

    var body: some View {
        ShareLink(item: note)
    }
}

Custom Label

ShareLink(item: note) {
    Label("Share Note", systemImage: "square.and.arrow.up")
}

With Preview

ShareLink(
    item: note,
    preview: SharePreview(
        note.title,
        image: Image(systemName: "doc.text")
    )
)

Sharing URL

ShareLink(
    item: URL(string: "https://yourapp.com/note/123")!,
    subject: Text("Check out this note"),
    message: Text("I thought you might find this interesting")
)

Sharing Image

struct PhotoView: View {
    let image: Image

    var body: some View {
        image
            .contextMenu {
                ShareLink(
                    item: image,
                    preview: SharePreview("Photo", image: image)
                )
            }
    }
}

Multiple Items

struct NotesListView: View {
    @State private var selectedNotes: Set<Note.ID> = []
    let notes: [Note]

    var body: some View {
        List(notes, selection: $selectedNotes) { note in
            Text(note.title)
        }
        .toolbar {
            ShareLink(
                items: notes.filter { selectedNotes.contains($0.id) }
            ) { note in
                SharePreview(note.title)
            }
        }
    }
}

Drag and Drop

Draggable

struct DraggableNoteView: View {
    let note: Note

    var body: some View {
        VStack {
            Text(note.title)
                .font(.headline)
            Text(note.content)
                .font(.body)
        }
        .draggable(note)
    }
}

Drop Destination

struct NoteDropZone: View {
    @State private var droppedNotes: [Note] = []

    var body: some View {
        VStack {
            Text("Drop notes here")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.gray.opacity(0.2))
        }
        .dropDestination(for: Note.self) { items, location in
            droppedNotes.append(contentsOf: items)
            return true
        } isTargeted: { isTargeted in
            // Visual feedback
        }
    }
}

Drop with Position

struct CanvasView: View {
    @State private var items: [(Note, CGPoint)] = []

    var body: some View {
        ZStack {
            ForEach(items, id: \.0.id) { item, position in
                NoteCard(note: item)
                    .position(position)
            }
        }
        .dropDestination(for: Note.self) { notes, location in
            for note in notes {
                items.append((note, location))
            }
            return true
        }
    }
}

Drag Preview

Text(note.title)
    .draggable(note) {
        // Custom drag preview
        VStack {
            Image(systemName: "doc.text")
                .font(.largeTitle)
            Text(note.title)
        }
        .padding()
        .background(.regularMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

List Reordering

struct ReorderableList: View {
    @State private var items = ["Item 1", "Item 2", "Item 3"]

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text(item)
            }
            .onMove { from, to in
                items.move(fromOffsets: from, toOffset: to)
            }
        }
    }
}

Copy and Paste

Copyable

struct CopyableText: View {
    let text: String

    var body: some View {
        Text(text)
            .textSelection(.enabled)  // Built-in copy
    }
}

// Or with custom type
struct NoteCard: View {
    let note: Note

    var body: some View {
        VStack {
            Text(note.title)
            Text(note.content)
        }
        .contextMenu {
            Button {
                UIPasteboard.general.string = note.plainText
            } label: {
                Label("Copy", systemImage: "doc.on.doc")
            }
        }
    }
}

PasteButton

struct PasteDestination: View {
    @State private var pastedNote: Note?

    var body: some View {
        VStack {
            if let note = pastedNote {
                NoteCard(note: note)
            } else {
                Text("No note pasted")
            }

            PasteButton(payloadType: Note.self) { notes in
                pastedNote = notes.first
            }
        }
    }
}

Pasteable Modifier

struct NoteEditor: View {
    @State private var content = ""

    var body: some View {
        TextEditor(text: $content)
            .pasteDestination(for: String.self) { strings in
                content += strings.joined(separator: "\n")
            }
    }
}

Exportable

File Exporter

struct DocumentView: View {
    @State private var showExporter = false
    let document: TextDocument

    var body: some View {
        Button("Export") {
            showExporter = true
        }
        .fileExporter(
            isPresented: $showExporter,
            document: document,
            contentType: .plainText,
            defaultFilename: "document.txt"
        ) { result in
            switch result {
            case .success(let url):
                print("Exported to: \(url)")
            case .failure(let error):
                print("Export failed: \(error)")
            }
        }
    }
}

struct TextDocument: FileDocument {
    static var readableContentTypes: [UTType] { [.plainText] }

    var text: String

    init(text: String = "") {
        self.text = text
    }

    init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            text = String(data: data, encoding: .utf8) ?? ""
        } else {
            text = ""
        }
    }

    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        FileWrapper(regularFileWithContents: text.data(using: .utf8)!)
    }
}

File Importer

struct ImporterView: View {
    @State private var showImporter = false
    @State private var importedContent = ""

    var body: some View {
        VStack {
            Text(importedContent)

            Button("Import") {
                showImporter = true
            }
        }
        .fileImporter(
            isPresented: $showImporter,
            allowedContentTypes: [.plainText],
            allowsMultipleSelection: false
        ) { result in
            switch result {
            case .success(let urls):
                guard let url = urls.first else { return }
                if url.startAccessingSecurityScopedResource() {
                    defer { url.stopAccessingSecurityScopedResource() }
                    importedContent = (try? String(contentsOf: url)) ?? ""
                }
            case .failure(let error):
                print("Import failed: \(error)")
            }
        }
    }
}

Universal Clipboard

Automatic with Transferable

When your type conforms to Transferable, it automatically works with Universal Clipboard across Apple devices with the same iCloud account.

// Copy on Mac
let note = Note(title: "Test", content: "Content")
NSPasteboard.general.writeObjects([note])

// Paste on iPhone
let notes = UIPasteboard.general.itemProviders
    .compactMap { try? await $0.loadTransferable(type: Note.self) }

Handoff Integration

struct NoteDetailView: View {
    let note: Note

    var body: some View {
        VStack {
            Text(note.title)
            Text(note.content)
        }
        .userActivity("com.yourapp.viewNote") { activity in
            activity.isEligibleForHandoff = true
            activity.userInfo = ["noteId": note.id.uuidString]
        }
    }
}

Best Practices

1. Order Representations by Specificity

static var transferRepresentation: some TransferRepresentation {
    // Most specific (native format)
    CodableRepresentation(contentType: .myCustomType)

    // Rich fallback
    DataRepresentation(contentType: .rtf) { ... }

    // Universal fallback
    ProxyRepresentation(exporting: \.plainText)
}

2. Provide Good Previews

ShareLink(
    item: photo,
    preview: SharePreview(
        photo.title,
        image: photo.thumbnail,  // Use thumbnail, not full image
        icon: Image(systemName: "photo")
    )
)

3. Handle Errors Gracefully

.dropDestination(for: Note.self) { items, location in
    do {
        try processDroppedItems(items)
        return true
    } catch {
        showError(error)
        return false
    }
}

4. Visual Drop Feedback

@State private var isTargeted = false

VStack { ... }
    .background(isTargeted ? Color.accentColor.opacity(0.2) : Color.clear)
    .dropDestination(for: Note.self) { items, location in
        // Handle drop
        return true
    } isTargeted: { targeted in
        withAnimation {
            isTargeted = targeted
        }
    }

5. Support Multiple Types

.dropDestination(for: String.self) { strings, _ in
    handleText(strings)
    return true
}
.dropDestination(for: URL.self) { urls, _ in
    handleURLs(urls)
    return true
}
.dropDestination(for: Data.self) { dataItems, _ in
    handleData(dataItems)
    return true
}

Official Resources