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.
/plugin marketplace add bluewaves-creations/bluewaves-skills/plugin install swift-apple-dev@bluewaves-skillsThis skill is limited to using the following tools:
Comprehensive guide to the Transferable protocol for drag and drop, copy/paste, ShareLink, and cross-app data transfer in SwiftUI.
Transferable is Swift's declarative way to make types shareable across apps and system features:
// Already Transferable
String.self
Data.self
URL.self
Image.self
AttributedString.self
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")
}
}
<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>
For Codable types:
struct Task: Codable, Transferable {
var id: UUID
var title: String
var isComplete: Bool
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .task)
}
}
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)
}
}
}
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)
}
}
}
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)"
}
}
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)
}
}
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)
}
}
}
struct ContentView: View {
let note: Note
var body: some View {
ShareLink(item: note)
}
}
ShareLink(item: note) {
Label("Share Note", systemImage: "square.and.arrow.up")
}
ShareLink(
item: note,
preview: SharePreview(
note.title,
image: Image(systemName: "doc.text")
)
)
ShareLink(
item: URL(string: "https://yourapp.com/note/123")!,
subject: Text("Check out this note"),
message: Text("I thought you might find this interesting")
)
struct PhotoView: View {
let image: Image
var body: some View {
image
.contextMenu {
ShareLink(
item: image,
preview: SharePreview("Photo", image: image)
)
}
}
}
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)
}
}
}
}
struct DraggableNoteView: View {
let note: Note
var body: some View {
VStack {
Text(note.title)
.font(.headline)
Text(note.content)
.font(.body)
}
.draggable(note)
}
}
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
}
}
}
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
}
}
}
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))
}
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)
}
}
}
}
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")
}
}
}
}
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
}
}
}
}
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")
}
}
}
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)!)
}
}
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)")
}
}
}
}
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) }
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]
}
}
}
static var transferRepresentation: some TransferRepresentation {
// Most specific (native format)
CodableRepresentation(contentType: .myCustomType)
// Rich fallback
DataRepresentation(contentType: .rtf) { ... }
// Universal fallback
ProxyRepresentation(exporting: \.plainText)
}
ShareLink(
item: photo,
preview: SharePreview(
photo.title,
image: photo.thumbnail, // Use thumbnail, not full image
icon: Image(systemName: "photo")
)
)
.dropDestination(for: Note.self) { items, location in
do {
try processDroppedItems(items)
return true
} catch {
showError(error)
return false
}
}
@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
}
}
.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
}
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.