Agent skill
transferable-sharing
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.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/transferable-sharing
SKILL.md
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
swift
// Already Transferable
String.self
Data.self
URL.self
Image.self
AttributedString.self
Basic Conformance
swift
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
xml
<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:
swift
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:
swift
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:
swift
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:
swift
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
swift
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
swift
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
swift
struct ContentView: View {
let note: Note
var body: some View {
ShareLink(item: note)
}
}
Custom Label
swift
ShareLink(item: note) {
Label("Share Note", systemImage: "square.and.arrow.up")
}
With Preview
swift
ShareLink(
item: note,
preview: SharePreview(
note.title,
image: Image(systemName: "doc.text")
)
)
Sharing URL
swift
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
swift
struct PhotoView: View {
let image: Image
var body: some View {
image
.contextMenu {
ShareLink(
item: image,
preview: SharePreview("Photo", image: image)
)
}
}
}
Multiple Items
swift
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
swift
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
swift
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
swift
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
swift
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
swift
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
swift
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
swift
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
swift
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
swift
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
swift
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.
swift
// 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
swift
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
swift
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
swift
ShareLink(
item: photo,
preview: SharePreview(
photo.title,
image: photo.thumbnail, // Use thumbnail, not full image
icon: Image(systemName: "photo")
)
)
3. Handle Errors Gracefully
swift
.dropDestination(for: Note.self) { items, location in
do {
try processDroppedItems(items)
return true
} catch {
showError(error)
return false
}
}
4. Visual Drop Feedback
swift
@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
swift
.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
Didn't find tool you were looking for?