From apple-dev
SwiftUI Transferable protocol and drag-and-drop patterns including custom transfer representations, dropDestination, draggable, and clipboard integration. Use when implementing drag-and-drop or copy/paste.
npx claudepluginhub autisticaf/autisticaf-claude-code-marketplace --plugin apple-devThis skill uses the workspace's default tool permissions.
> **First step:** Tell the user: "swiftui-transferable skill loaded."
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
First step: Tell the user: "swiftui-transferable skill loaded."
Patterns for the Transferable protocol, drag-and-drop, clipboard integration, ShareLink, and file import/export in SwiftUI.
Use this skill when the user:
What data transfer mechanism do you need?
|
+- SwiftUI-native drag/drop with modern types
| +- Use Transferable protocol (iOS 16+)
|
+- Interop with UIKit or AppKit drag sessions
| +- Use NSItemProvider (bridged via Transferable when possible)
|
+- Direct pasteboard read/write without SwiftUI modifiers
| +- iOS: UIPasteboard.general
| +- macOS: NSPasteboard.general
|
+- Sharing content via share sheet
| +- Use ShareLink with Transferable items
|
+- File open/save panels
+- Use .fileImporter / .fileExporter with Transferable
| API | Minimum Version | Notes |
|---|---|---|
Transferable protocol | iOS 16, macOS 13 | Core protocol |
CodableRepresentation | iOS 16, macOS 13 | JSON-encoded Codable types |
DataRepresentation | iOS 16, macOS 13 | Raw Data conversion |
FileRepresentation | iOS 16, macOS 13 | File-based transfers |
ProxyRepresentation | iOS 16, macOS 13 | Delegate to another Transferable |
.draggable() | iOS 16, macOS 13 | Drag source modifier |
.dropDestination(for:action:isTargeted:) | iOS 16, macOS 13 | Drop target modifier |
ShareLink | iOS 16, macOS 13 | Share sheet integration |
.fileImporter() | iOS 14, macOS 11 | File open panel |
.fileExporter() | iOS 14, macOS 11 | File save panel |
PasteButton | iOS 16, macOS 13 | System paste button |
Best for Codable model types shared within your app or with other apps that understand your content type:
struct TodoItem: Codable, Transferable {
var title: String
var isCompleted: Bool
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .json)
}
}
For custom binary encoding or when you need manual control over serialization:
struct Diagram: Transferable {
var paths: [Path]
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(contentType: .diagram) { diagram in
try DiagramEncoder().encode(diagram)
} importing: { data in
try DiagramDecoder().decode(from: data)
}
}
}
For large payloads best transferred as files:
struct VideoProject: Transferable {
var fileURL: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .mpeg4Movie) { project in
SentTransferredFile(project.fileURL)
} importing: { received in
let destination = FileManager.default.temporaryDirectory
.appendingPathComponent(received.file.lastPathComponent)
try FileManager.default.copyItem(at: received.file, to: destination)
return Self(fileURL: destination)
}
}
}
Delegate transfer to an existing Transferable type (String, Image, URL, etc.):
struct Contact: Transferable {
var name: String
var email: String
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.name)
}
}
Offer the same item in multiple formats so the receiver picks the best one. Representations are tried in declaration order:
struct RichNote: Codable, Transferable {
var title: String
var body: String
static var transferRepresentation: some TransferRepresentation {
// Preferred: full fidelity
CodableRepresentation(contentType: .json)
// Fallback: plain text for any text receiver
ProxyRepresentation(exporting: \.body)
}
}
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
ForEach(photos) { photo in
PhotoThumbnail(photo: photo)
.draggable(photo) {
// Custom drag preview
PhotoThumbnail(photo: photo)
.frame(width: 80, height: 80)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
}
@State private var droppedPhotos: [Photo] = []
@State private var isTargeted = false
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(isTargeted ? Color.accentColor.opacity(0.2) : Color.gray.opacity(0.1))
Text(droppedPhotos.isEmpty ? "Drop photos here" : "\(droppedPhotos.count) photos")
}
.dropDestination(for: Photo.self) { photos, location in
droppedPhotos.append(contentsOf: photos)
return true
} isTargeted: { targeted in
isTargeted = targeted
}
Use .onMove for simple list reordering, or combine .draggable with .dropDestination for custom reorder logic:
// Simple reordering
List {
ForEach(items) { item in
Text(item.title)
}
.onMove { source, destination in
items.move(fromOffsets: source, toOffset: destination)
}
}
Custom drag-based reordering:
ForEach(items) { item in
Text(item.title)
.draggable(item)
.dropDestination(for: TodoItem.self) { droppedItems, _ in
guard let source = droppedItems.first,
let fromIndex = items.firstIndex(of: source),
let toIndex = items.firstIndex(of: item) else { return false }
items.move(fromOffsets: IndexSet(integer: fromIndex),
toOffset: toIndex > fromIndex ? toIndex + 1 : toIndex)
return true
}
}
// Single item
ShareLink(item: note, preview: SharePreview(note.title))
// Collection
ShareLink(items: selectedPhotos) { photo in
SharePreview(photo.caption, image: photo.thumbnail)
}
The system PasteButton grants paste permission without a confirmation prompt:
PasteButton(payloadType: String.self) { strings in
guard let text = strings.first else { return }
content = text
}
// iOS
UIPasteboard.general.string = note.body
if let text = UIPasteboard.general.string { content = text }
// macOS
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(note.body, forType: .string)
// Import
@State private var showImporter = false
Button("Import") { showImporter = true }
.fileImporter(
isPresented: $showImporter,
allowedContentTypes: [.plainText, .json]
) { result in
if case .success(let url) = result {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
document = try? TextDocument(fileURL: url)
}
}
// Export
@State private var showExporter = false
Button("Export") { showExporter = true }
.fileExporter(
isPresented: $showExporter,
document: document,
contentType: .plainText,
defaultFilename: "export.txt"
) { result in
if case .failure(let error) = result {
print("Export failed: \(error.localizedDescription)")
}
}
// ❌ Using .data — too generic, receivers can't identify your content
CodableRepresentation(contentType: .data)
// ✅ Use a specific UTType
CodableRepresentation(contentType: .json)
// ❌ Ignoring the return value — system uses it for the drop animation
.dropDestination(for: Photo.self) { photos, location in
droppedPhotos.append(contentsOf: photos)
// Missing return — defaults to Void which won't compile
}
// ✅ Return true on success, false on failure
.dropDestination(for: Photo.self) { photos, location in
droppedPhotos.append(contentsOf: photos)
return true
} isTargeted: { targeted in
isTargeted = targeted
}
// ❌ Reading imported file without security scope — may fail silently
if case .success(let url) = result {
let data = try? Data(contentsOf: url)
}
// ✅ Always bracket with security-scoped access
if case .success(let url) = result {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
let data = try? Data(contentsOf: url)
}
// ❌ Lossy representation first — receivers prefer it over full-fidelity
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.title)
CodableRepresentation(contentType: .json)
}
// ✅ Highest fidelity first, fallbacks after
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .json)
ProxyRepresentation(exporting: \.title)
}