Use when implementing drag and drop, copy/paste, ShareLink, or ANY content sharing between apps or views - covers Transferable protocol, TransferRepresentation types, UTType declarations, SwiftUI surfaces, and NSItemProvider bridging
BY CharlesWiltgenUPDATED Oct 27, 2026
INTENDED FOR◈OpenClawInstall in a listed agent.
instructions.md
---
name: axiom-transferable-ref
description: Use when implementing drag and drop, copy/paste, ShareLink, or ANY content sharing between apps or views - covers Transferable protocol, TransferRepresentation types, UTType declarations, SwiftUI surfaces, and NSItemProvider bridging
license: MIT
metadata:
version: "1.0.0"
---
# Transferable & Content Sharing Reference
Comprehensive guide to the CoreTransferable framework and SwiftUI sharing surfaces: drag and drop, copy/paste, and ShareLink.
## When to Use This Skill
- Implementing drag and drop (`.draggable`, `.dropDestination`)
- Adding copy/paste support (`.copyable`, `.pasteDestination`, `PasteButton`)
- Sharing content via `ShareLink`
- Making custom types transferable
- Declaring custom UTTypes for app-specific formats
- Bridging `Transferable` types with UIKit's `NSItemProvider`
- Choosing between `CodableRepresentation`, `DataRepresentation`, `FileRepresentation`, and `ProxyRepresentation`
## Example Prompts
"How do I make my model draggable in SwiftUI?"
"ShareLink isn't showing my custom preview"
"How do I accept dropped files in my view?"
"What's the difference between DataRepresentation and FileRepresentation?"
"How do I add copy/paste support for my custom type?"
"My drag and drop works within the app but not across apps"
"How do I declare a custom UTType?"
---
## Part 1: Quick Reference
### Decision Tree: Which TransferRepresentation?
```
Your model type...
├─ Conforms to Codable + no specific binary format needed?
│ → CodableRepresentation
├─ Has custom binary format (Data in memory)?
│ → DataRepresentation (exporting/importing closures)
├─ Lives on disk (large files, videos, documents)?
│ → FileRepresentation (passes file URLs, not bytes)
├─ Need a fallback for receivers that don't understand your type?
│ → Add ProxyRepresentation (e.g., export as String or URL)
└─ Need to conditionally hide a representation?
→ Apply .exportingCondition to any representation
```
### Common Errors
| Error / Symptom | Cause | Fix |
|-----------------|-------|-----|
| "Type does not conform to Transferable" | Missing `transferRepresentation` | Add `static var transferRepresentation: some TransferRepresentation` |
| Drop works in-app but not across apps | Custom UTType not declared in Info.plist | Add `UTExportedTypeDeclarations` entry |
| Receiver always gets plain text instead of rich type | ProxyRepresentation listed before CodableRepresentation | Reorder: richest representation first |
| FileRepresentation crashes with "file not found" | Receiver didn't copy file before sandbox extension expired | Copy to app storage in the importing closure |
| PasteButton always disabled | Pasteboard doesn't contain matching Transferable type | Check UTType conformance; verify the pasted data matches |
| ShareLink shows generic preview | No `SharePreview` provided or image isn't `Transferable` | Supply explicit `SharePreview` with title and image |
| `.dropDestination` closure never fires | Wrong payload type or view has zero hit-test area | Verify `for:` type matches dragged content; add `.frame()` or `.contentShape()` |
### Built-in Transferable Types
These work with zero additional code — no conformance needed:
`String`, `Data`, `URL`, `AttributedString`, `Image`, `Color`
---
## Part 2: Making Types Transferable
The `Transferable` protocol has one requirement: a static `transferRepresentation` property.
### CodableRepresentation
Best for: models already conforming to `Codable`. Uses JSON by default.
```swift
import UniformTypeIdentifiers
extension UTType {
static var todo: UTType = UTType(exportedAs: "com.example.todo")
}
struct Todo: Codable, Transferable {
var text: String
var isDone: Bool
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .todo)
}
}
```
Custom encoder/decoder (e.g., PropertyList instead of JSON):
```swift
CodableRepresentation(
contentType: .todo,
encoder: PropertyListEncoder(),
decoder: PropertyListDecoder()
)
```
**Requirement**: Custom UTTypes need matching `UTExportedTypeDeclarations` in Info.plist (see Part 4).
### DataRepresentation
Best for: custom binary formats where data is in memory and you control serialization.
```swift
struct ProfilesArchive: Transferable {
var profiles: [Profile]
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.toCSV()
} importing: { data in
try ProfilesArchive(csvData: data)
}
}
}
```
Import-only or export-only variants:
```swift
// Import only
DataRepresentation(importedContentType: .png) { data in
try MyImage(pngData: data)
}
// Export only
DataRepresentation(exportedContentType: .png) { image in
try image.pngData()
}
```
**Avoid** using `UTType.data` as the content type — use a specific type like `.png`, `.pdf`, `.commaSeparatedText`.
### FileRepresentation
Best for: large payloads on disk (videos, documents, archives). Passes file URLs instead of loading bytes into memory.
```swift
struct Video: Transferable {
let file: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// MUST copy — sandbox extension is temporary
let dest = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mp4")
try FileManager.default.copyItem(at: received.file, to: dest)
return Video(file: dest)
}
}
}
```
**Critical**: The `received.file` URL has a temporary sandbox extension. Copy the file to your own storage in the importing closure — the URL becomes inaccessible after the closure returns.
`SentTransferredFile` properties:
- `file: URL` — the file location
- `allowAccessingOriginalFile: Bool` — when `false` (default), receiver gets a copy
`ReceivedTransferredFile` properties:
- `file: URL` — the received file on disk
- `isOriginalFile: Bool` — whether this is the sender's original file or a copy
**Content type precision**: `.mpeg4Movie` only matches `.mp4` files. To accept all common video formats (`.mp4`, `.mov`, `.m4v`), use the parent type `.movie` — or declare multiple `FileRepresentation`s for specific subtypes:
```swift
// Broad: accept any video format the system recognizes
FileRepresentation(contentType: .movie) { ... } importing: { ... }
// Or specific: separate handlers per format
FileRepresentation(contentType: .mpeg4Movie) { ... } importing: { ... }
FileRepresentation(contentType: .quickTimeMovie) { ... } importing: { ... }
```
**Import-only**: When your type only receives files (drop target, no export), use the import-only initializer — it makes intent explicit and avoids accidental export:
```swift
FileRepresentation(importedContentType: .movie) { received in
let dest = appStorageURL.appendingPathComponent(received.file.lastPathComponent)
try FileManager.default.copyItem(at: received.file, to: dest)
return VideoClip(localURL: dest)
}
```
### ProxyRepresentation
Best for: fallback representations that let your type work with receivers expecting simpler types.
```swift
struct Profile: Transferable {
var name: String
var avatar: Image
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile)
ProxyRepresentation(exporting: \.name) // Fallback: paste as text
}
}
```
Export-only proxy (common pattern — reverse conversion often impossible):
```swift
ProxyRepresentation(exporting: \.name) // Profile → String (one-way)
```
Bidirectional proxy (when reverse makes sense):
```swift
ProxyRepresentation { item in
item.name // export
} importing: { name in
Profile(name: name) // import
}
```
### Combining Multiple Representations
List representations in the `transferRepresentation` body. **Order matters** — receivers use the first representation they support.
```swift
struct Profile: Transferable {
static var transferRepresentation: some TransferRepresentation {
// 1. Richest: full profile data (apps that understand .profile)
CodableRepresentation(contentType: .profile)
// 2. Fallback: plain text (text fields, notes, any app)
ProxyRepresentation(exporting: \.name)
}
}
```
**Common mistake**: putting `ProxyRepresentation` first causes receivers that support both to always get the degraded version.
### Conditional Export
Hide a representation at runtime when conditions aren't met:
```swift
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.toCSV()
} importing: { data in
try Self(csvData: data)
}
.exportingCondition { archive in
archive.supportsCSV
}
```
### Visibility
Control which processes can see a representation:
```swift
CodableRepresentation(contentType: .profile)
.visibility(.ownProcess) // Only within this app
```
Options: `.all` (default), `.team` (same developer team), `.group` (same App Group, macOS), `.ownProcess` (same app only)
### Suggested File Name
Hint for receivers writing to disk:
```swift
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// ...
}
.suggestedFileName("My Video.mp4")
// Or dynamic:
.suggestedFileName { video in video.title + ".mp4" }
```
---
## Part 3: SwiftUI Surfaces
### ShareLink
The standard sharing entry point. Accepts any `Transferable` type.
```swift
// Simple: share a string
ShareLink(item: "Check out this app!")
// With preview
ShareLink(
item: photo,
preview: SharePreview(photo.caption, image: photo.image)
)
// Share a URL with custom preview (prevents system metadata fetch)
ShareLink(
item: URL(string: "https://example.com")!,
preview: SharePreview("My Site", image: Image("hero"))
)
```
Sharing multiple items with per-item previews:
```swift
ShareLink(items: photos) { photo in
SharePreview(photo.caption, image: photo.image)
}
```
`SharePreview` initializers:
- `SharePreview("Title")` — text only
- `SharePreview("Title", image: someImage)` — text + full-size image
- `SharePreview("Title", icon: someIcon)` — text + thumbnail icon
- `SharePreview("Title", image: someImage, icon: someIcon)` — all three
**Gotcha**: If you omit `SharePreview` for a custom type, the share sheet shows a generic preview. Always provide one for non-trivial types.
### Drag and Drop
**Making a view draggable:**
```swift
Text(profile.name)
.draggable(profile)
```
With custom drag preview:
```swift
Text(profile.name)
.draggable(profile) {
Label(profile.name, systemImage: "person")
.padding()
.background(.regularMaterial)
}
```
**Accepting drops:**
```swift
Color.clear
.frame(width: 200, height: 200)
.dropDestination(for: Profile.self) { profiles, location in
guard let profile = profiles.first else { return false }
self.droppedProfile = profile
return true
} isTargeted: { isTargeted in
self.isDropTargeted = isTargeted
}
```
**Multiple item types** — use an enum wrapper conforming to `Transferable` rather than stacking `.dropDestination` modifiers (stacking may cause only the outermost handler to fire):
```swift
enum DroppableItem: Transferable {
case image(Image)
case text(String)
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation { (image: Image) in DroppableItem.image(image) }
ProxyRepresentation { (text: String) in DroppableItem.text(text) }
}
}
myView
.dropDestination(for: DroppableItem.self) { items, _ in
for item in items {
switch item {
case .image(let img): handleImage(img)
case .text(let str): handleString(str)
}
}
return true
}
```
**ForEach with reordering** — combine with `.onMove` or use `draggable`/`dropDestination` for cross-container moves.
### Clipboard (Copy/Paste)
**Copy support** (activates Edit > Copy / Cmd+C):
```swift
List(items) { item in
Text(item.name)
}
.copyable(items)
```
**Paste support** (activates Edit > Paste / Cmd+V):
```swift
List(items) { item in
Text(item.name)
}
.pasteDestination(for: Item.self) { pasted in
items.append(contentsOf: pasted)
} validator: { candidates in
candidates.filter { $0.isValid }
}
```
The validator closure runs before the action — return an empty array to prevent the paste.
**Cut support:**
```swift
.cuttable(for: Item.self) {
let selected = items.filter { $0.isSelected }
items.removeAll { $0.isSelected }
return selected
}
```
**PasteButton** — system button that handles paste with type filtering:
```swift
PasteButton(payloadType: String.self) { strings in
notes.append(contentsOf: strings)
}
```
Platform difference: PasteButton auto-validates pasteboard changes on iOS but not on macOS.
**Availability**: `.copyable`, `.pasteDestination`, and `.cuttable` are **macOS 13+ only** — they do not exist on iOS. On iOS, use `PasteButton` (iOS 16+) for paste, and standard context menus or `UIPasteboard` for programmatic copy/cut. `PasteButton` is cross-platform: macOS 10.15+, iOS 16+, visionOS 1.0+.
---
## Part 4: UTType Declarations
### System Types
Use Apple's built-in UTTypes when possible — they're already recognized across the system:
```swift
import UniformTypeIdentifiers
// Common types
UTType.plainText // public.plain-text
UTType.utf8PlainText // public.utf8-plain-text
UTType.json // public.json
UTType.png // public.png
UTType.jpeg // public.jpeg
UTType.pdf // com.adobe.pdf
UTType.mpeg4Movie // public.mpeg-4
UTType.commaSeparatedText // public.comma-separated-values-text
```
### Declaring Custom Types
**Step 1**: Declare in Swift:
```swift
extension UTType {
static var recipe: UTType = UTType(exportedAs: "com.myapp.recipe")
}
```
**Step 2**: Add to Info.plist under `UTExportedTypeDeclarations`:
```xml
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.myapp.recipe</string>
<key>UTTypeDescription</key>
<string>Recipe</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>recipe</string>
</array>
</dict>
</dict>
</array>
```
**Both are required.** The Swift declaration alone makes it compile, but cross-app transfers silently fail without the Info.plist entry.
### Imported vs Exported Types
- **Exported** (`exportedAs:`) — Your app owns this type. Use for app-specific formats.
- **Imported** (`importedAs:`) — Another app owns this type. Use when you want to accept their format.
### UTType Conformance
Custom types should conform to system types for broader compatibility:
```swift
// Your .recipe conforms to public.data (binary data)
// This means any receiver that accepts generic data can also accept recipes
```
Common conformance parents: `public.data`, `public.content`, `public.text`, `public.image`
---
## Part 5: UIKit Bridging
### NSItemProvider + Transferable
Bridge between UIKit's `NSItemProvider` (used by `UIActivityViewController`, extensions, drag sessions) and `Transferable`:
```swift
// Load a Transferable from an NSItemProvider
let provider: NSItemProvider = // from drag session, extension, etc.
provider.loadTransferable(type: Profile.self) { result in
switch result {
case .success(let profile):
// Use the profile
case .failure(let error):
// Handle error
}
}
```
### When to Use UIActivityViewController
`ShareLink` covers most sharing needs. Use `UIActivityViewController` when you need:
- Custom activity items or excluded activity types
- `UIActivityItemsConfiguration` for lazy item provision
- Custom `UIActivity` subclasses
- Programmatic presentation control
```swift
struct ShareSheet: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}
```
For most apps, `ShareLink` is sufficient and preferred — it integrates with `Transferable` natively.
---
## Part 6: Gotchas & Troubleshooting
### FileRepresentation Temporary File Lifecycle
The `received.file` URL in a `FileRepresentation` importing closure has a temporary sandbox extension. The system may revoke access after the closure returns. Always copy the file:
```swift
// WRONG — file may become inaccessible
return Video(file: received.file)
// RIGHT — copy to your own storage
let dest = myAppDirectory.appendingPathComponent(received.file.lastPathComponent)
try FileManager.default.copyItem(at: received.file, to: dest)
return Video(file: dest)
```
### Async Work After File Drop
The `FileRepresentation` importing closure is synchronous — you cannot `await` inside it. Copy the file first, return the model, then do async post-processing (thumbnails, transcoding, metadata extraction) on the copied URL:
```swift
// WRONG — can't await in the importing closure
FileRepresentation(importedContentType: .movie) { received in
let dest = ...
try FileManager.default.copyItem(at: received.file, to: dest)
let thumbnail = await generateThumbnail(for: dest) // ❌ compile error
return VideoClip(localURL: dest, thumbnail: thumbnail)
}
// RIGHT — return immediately, process async afterward
// In your view model or drop handler:
.dropDestination(for: VideoClip.self) { clips, _ in
for clip in clips {
timeline.append(clip)
Task {
// clip.localURL is the COPY — safe to access anytime
let thumbnail = await generateThumbnail(for: clip.localURL)
clip.thumbnail = thumbnail
}
}
return true
}
```
### Representation Ordering
Representations are tried **in declaration order**. The receiver uses the first one it supports.
```swift
// WRONG — receivers always get plain text
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.name) // ← every receiver supports String
CodableRepresentation(contentType: .profile) // ← never reached
}
// RIGHT — richest first, fallbacks last
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile) // ← apps that understand Profile
ProxyRepresentation(exporting: \.name) // ← fallback for everyone else
}
```
### Custom UTType Without Info.plist
If you declare `UTType(exportedAs: "com.myapp.type")` in Swift but forget the Info.plist entry:
- In-app transfers work (same process recognizes the type)
- Cross-app transfers silently fail (other apps can't resolve the type)
This is the most common "works in development, fails in production" issue.
### Drop Target Hit Testing
`.dropDestination` requires the view to have a non-zero frame for hit testing. If drops aren't registering:
```swift
// WRONG — Color.clear has zero intrinsic size
Color.clear
.dropDestination(for: Image.self) { ... }
// RIGHT — give it a frame
Color.clear
.frame(width: 200, height: 200)
.contentShape(Rectangle()) // ensure full area is hit-testable
.dropDestination(for: Image.self) { ... }
```
### Async Loading with loadTransferable
`NSItemProvider.loadTransferable` is asynchronous. Update UI on the main actor:
```swift
provider.loadTransferable(type: Profile.self) { result in
Task { @MainActor in
switch result {
case .success(let profile):
self.profile = profile
case .failure(let error):
self.errorMessage = error.localizedDescription
}
}
}
```
### PasteButton Platform Differences
`PasteButton` auto-validates against pasteboard changes on iOS — the button enables/disables as the pasteboard content changes. On macOS, this automatic validation does not occur. If your macOS app needs dynamic paste validation, monitor `UIPasteboard.changedNotification` (UIKit) or `NSPasteboard` change count manually.
---
## Resources
**WWDC**: 2022-10062, 2022-10052, 2022-10023, 2022-10093, 2022-10095
**Docs**: /coretransferable/transferable, /coretransferable/choosing-a-transfer-representation-for-a-model-type, /coretransferable/filerepresentation, /coretransferable/proxyrepresentation, /swiftui/sharelink, /swiftui/drag-and-drop, /swiftui/clipboard, /uniformtypeidentifiers
**Skills**: axiom-photo-library, axiom-codable, axiom-swiftui-gestures, axiom-app-intents-ref
---
name: axiom-transferable-ref
description: Use when implementing drag and drop, copy/paste, ShareLink, or ANY content sharing between apps or views - covers Transferable protocol, TransferRepresentation types, UTType declarations, SwiftUI surfaces, and NSItemProvider bridging
license: MIT
metadata:
version: "1.0.0"
---
# Transferable & Content Sharing Reference
Comprehensive guide to the CoreTransferable framework and SwiftUI sharing surfaces: drag and drop, copy/paste, and ShareLink.
## When to Use This Skill
- Implementing drag and drop (`.draggable`, `.dropDestination`)
- Adding copy/paste support (`.copyable`, `.pasteDestination`, `PasteButton`)
- Sharing content via `ShareLink`
- Making custom types transferable
- Declaring custom UTTypes for app-specific formats
- Bridging `Transferable` types with UIKit's `NSItemProvider`
- Choosing between `CodableRepresentation`, `DataRepresentation`, `FileRepresentation`, and `ProxyRepresentation`
## Example Prompts
"How do I make my model draggable in SwiftUI?"
"ShareLink isn't showing my custom preview"
"How do I accept dropped files in my view?"
"What's the difference between DataRepresentation and FileRepresentation?"
"How do I add copy/paste support for my custom type?"
"My drag and drop works within the app but not across apps"
"How do I declare a custom UTType?"
---
## Part 1: Quick Reference
### Decision Tree: Which TransferRepresentation?
```
Your model type...
├─ Conforms to Codable + no specific binary format needed?
│ → CodableRepresentation
├─ Has custom binary format (Data in memory)?
│ → DataRepresentation (exporting/importing closures)
├─ Lives on disk (large files, videos, documents)?
│ → FileRepresentation (passes file URLs, not bytes)
├─ Need a fallback for receivers that don't understand your type?
│ → Add ProxyRepresentation (e.g., export as String or URL)
└─ Need to conditionally hide a representation?
→ Apply .exportingCondition to any representation
```
### Common Errors
| Error / Symptom | Cause | Fix |
|-----------------|-------|-----|
| "Type does not conform to Transferable" | Missing `transferRepresentation` | Add `static var transferRepresentation: some TransferRepresentation` |
| Drop works in-app but not across apps | Custom UTType not declared in Info.plist | Add `UTExportedTypeDeclarations` entry |
| Receiver always gets plain text instead of rich type | ProxyRepresentation listed before CodableRepresentation | Reorder: richest representation first |
| FileRepresentation crashes with "file not found" | Receiver didn't copy file before sandbox extension expired | Copy to app storage in the importing closure |
| PasteButton always disabled | Pasteboard doesn't contain matching Transferable type | Check UTType conformance; verify the pasted data matches |
| ShareLink shows generic preview | No `SharePreview` provided or image isn't `Transferable` | Supply explicit `SharePreview` with title and image |
| `.dropDestination` closure never fires | Wrong payload type or view has zero hit-test area | Verify `for:` type matches dragged content; add `.frame()` or `.contentShape()` |
### Built-in Transferable Types
These work with zero additional code — no conformance needed:
`String`, `Data`, `URL`, `AttributedString`, `Image`, `Color`
---
## Part 2: Making Types Transferable
The `Transferable` protocol has one requirement: a static `transferRepresentation` property.
### CodableRepresentation
Best for: models already conforming to `Codable`. Uses JSON by default.
```swift
import UniformTypeIdentifiers
extension UTType {
static var todo: UTType = UTType(exportedAs: "com.example.todo")
}
struct Todo: Codable, Transferable {
var text: String
var isDone: Bool
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .todo)
}
}
```
Custom encoder/decoder (e.g., PropertyList instead of JSON):
```swift
CodableRepresentation(
contentType: .todo,
encoder: PropertyListEncoder(),
decoder: PropertyListDecoder()
)
```
**Requirement**: Custom UTTypes need matching `UTExportedTypeDeclarations` in Info.plist (see Part 4).
### DataRepresentation
Best for: custom binary formats where data is in memory and you control serialization.
```swift
struct ProfilesArchive: Transferable {
var profiles: [Profile]
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.toCSV()
} importing: { data in
try ProfilesArchive(csvData: data)
}
}
}
```
Import-only or export-only variants:
```swift
// Import only
DataRepresentation(importedContentType: .png) { data in
try MyImage(pngData: data)
}
// Export only
DataRepresentation(exportedContentType: .png) { image in
try image.pngData()
}
```
**Avoid** using `UTType.data` as the content type — use a specific type like `.png`, `.pdf`, `.commaSeparatedText`.
### FileRepresentation
Best for: large payloads on disk (videos, documents, archives). Passes file URLs instead of loading bytes into memory.
```swift
struct Video: Transferable {
let file: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// MUST copy — sandbox extension is temporary
let dest = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mp4")
try FileManager.default.copyItem(at: received.file, to: dest)
return Video(file: dest)
}
}
}
```
**Critical**: The `received.file` URL has a temporary sandbox extension. Copy the file to your own storage in the importing closure — the URL becomes inaccessible after the closure returns.
`SentTransferredFile` properties:
- `file: URL` — the file location
- `allowAccessingOriginalFile: Bool` — when `false` (default), receiver gets a copy
`ReceivedTransferredFile` properties:
- `file: URL` — the received file on disk
- `isOriginalFile: Bool` — whether this is the sender's original file or a copy
**Content type precision**: `.mpeg4Movie` only matches `.mp4` files. To accept all common video formats (`.mp4`, `.mov`, `.m4v`), use the parent type `.movie` — or declare multiple `FileRepresentation`s for specific subtypes:
```swift
// Broad: accept any video format the system recognizes
FileRepresentation(contentType: .movie) { ... } importing: { ... }
// Or specific: separate handlers per format
FileRepresentation(contentType: .mpeg4Movie) { ... } importing: { ... }
FileRepresentation(contentType: .quickTimeMovie) { ... } importing: { ... }
```
**Import-only**: When your type only receives files (drop target, no export), use the import-only initializer — it makes intent explicit and avoids accidental export:
```swift
FileRepresentation(importedContentType: .movie) { received in
let dest = appStorageURL.appendingPathComponent(received.file.lastPathComponent)
try FileManager.default.copyItem(at: received.file, to: dest)
return VideoClip(localURL: dest)
}
```
### ProxyRepresentation
Best for: fallback representations that let your type work with receivers expecting simpler types.
```swift
struct Profile: Transferable {
var name: String
var avatar: Image
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile)
ProxyRepresentation(exporting: \.name) // Fallback: paste as text
}
}
```
Export-only proxy (common pattern — reverse conversion often impossible):
```swift
ProxyRepresentation(exporting: \.name) // Profile → String (one-way)
```
Bidirectional proxy (when reverse makes sense):
```swift
ProxyRepresentation { item in
item.name // export
} importing: { name in
Profile(name: name) // import
}
```
### Combining Multiple Representations
List representations in the `transferRepresentation` body. **Order matters** — receivers use the first representation they support.
```swift
struct Profile: Transferable {
static var transferRepresentation: some TransferRepresentation {
// 1. Richest: full profile data (apps that understand .profile)
CodableRepresentation(contentType: .profile)
// 2. Fallback: plain text (text fields, notes, any app)
ProxyRepresentation(exporting: \.name)
}
}
```
**Common mistake**: putting `ProxyRepresentation` first causes receivers that support both to always get the degraded version.
### Conditional Export
Hide a representation at runtime when conditions aren't met:
```swift
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.toCSV()
} importing: { data in
try Self(csvData: data)
}
.exportingCondition { archive in
archive.supportsCSV
}
```
### Visibility
Control which processes can see a representation:
```swift
CodableRepresentation(contentType: .profile)
.visibility(.ownProcess) // Only within this app
```
Options: `.all` (default), `.team` (same developer team), `.group` (same App Group, macOS), `.ownProcess` (same app only)
### Suggested File Name
Hint for receivers writing to disk:
```swift
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// ...
}
.suggestedFileName("My Video.mp4")
// Or dynamic:
.suggestedFileName { video in video.title + ".mp4" }
```
---
## Part 3: SwiftUI Surfaces
### ShareLink
The standard sharing entry point. Accepts any `Transferable` type.
```swift
// Simple: share a string
ShareLink(item: "Check out this app!")
// With preview
ShareLink(
item: photo,
preview: SharePreview(photo.caption, image: photo.image)
)
// Share a URL with custom preview (prevents system metadata fetch)
ShareLink(
item: URL(string: "https://example.com")!,
preview: SharePreview("My Site", image: Image("hero"))
)
```
Sharing multiple items with per-item previews:
```swift
ShareLink(items: photos) { photo in
SharePreview(photo.caption, image: photo.image)
}
```
`SharePreview` initializers:
- `SharePreview("Title")` — text only
- `SharePreview("Title", image: someImage)` — text + full-size image
- `SharePreview("Title", icon: someIcon)` — text + thumbnail icon
- `SharePreview("Title", image: someImage, icon: someIcon)` — all three
**Gotcha**: If you omit `SharePreview` for a custom type, the share sheet shows a generic preview. Always provide one for non-trivial types.
### Drag and Drop
**Making a view draggable:**
```swift
Text(profile.name)
.draggable(profile)
```
With custom drag preview:
```swift
Text(profile.name)
.draggable(profile) {
Label(profile.name, systemImage: "person")
.padding()
.background(.regularMaterial)
}
```
**Accepting drops:**
```swift
Color.clear
.frame(width: 200, height: 200)
.dropDestination(for: Profile.self) { profiles, location in
guard let profile = profiles.first else { return false }
self.droppedProfile = profile
return true
} isTargeted: { isTargeted in
self.isDropTargeted = isTargeted
}
```
**Multiple item types** — use an enum wrapper conforming to `Transferable` rather than stacking `.dropDestination` modifiers (stacking may cause only the outermost handler to fire):
```swift
enum DroppableItem: Transferable {
case image(Image)
case text(String)
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation { (image: Image) in DroppableItem.image(image) }
ProxyRepresentation { (text: String) in DroppableItem.text(text) }
}
}
myView
.dropDestination(for: DroppableItem.self) { items, _ in
for item in items {
switch item {
case .image(let img): handleImage(img)
case .text(let str): handleString(str)
}
}
return true
}
```
**ForEach with reordering** — combine with `.onMove` or use `draggable`/`dropDestination` for cross-container moves.
### Clipboard (Copy/Paste)
**Copy support** (activates Edit > Copy / Cmd+C):
```swift
List(items) { item in
Text(item.name)
}
.copyable(items)
```
**Paste support** (activates Edit > Paste / Cmd+V):
```swift
List(items) { item in
Text(item.name)
}
.pasteDestination(for: Item.self) { pasted in
items.append(contentsOf: pasted)
} validator: { candidates in
candidates.filter { $0.isValid }
}
```
The validator closure runs before the action — return an empty array to prevent the paste.
**Cut support:**
```swift
.cuttable(for: Item.self) {
let selected = items.filter { $0.isSelected }
items.removeAll { $0.isSelected }
return selected
}
```
**PasteButton** — system button that handles paste with type filtering:
```swift
PasteButton(payloadType: String.self) { strings in
notes.append(contentsOf: strings)
}
```
Platform difference: PasteButton auto-validates pasteboard changes on iOS but not on macOS.
**Availability**: `.copyable`, `.pasteDestination`, and `.cuttable` are **macOS 13+ only** — they do not exist on iOS. On iOS, use `PasteButton` (iOS 16+) for paste, and standard context menus or `UIPasteboard` for programmatic copy/cut. `PasteButton` is cross-platform: macOS 10.15+, iOS 16+, visionOS 1.0+.
---
## Part 4: UTType Declarations
### System Types
Use Apple's built-in UTTypes when possible — they're already recognized across the system:
```swift
import UniformTypeIdentifiers
// Common types
UTType.plainText // public.plain-text
UTType.utf8PlainText // public.utf8-plain-text
UTType.json // public.json
UTType.png // public.png
UTType.jpeg // public.jpeg
UTType.pdf // com.adobe.pdf
UTType.mpeg4Movie // public.mpeg-4
UTType.commaSeparatedText // public.comma-separated-values-text
```
### Declaring Custom Types
**Step 1**: Declare in Swift:
```swift
extension UTType {
static var recipe: UTType = UTType(exportedAs: "com.myapp.recipe")
}
```
**Step 2**: Add to Info.plist under `UTExportedTypeDeclarations`:
```xml
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.myapp.recipe</string>
<key>UTTypeDescription</key>
<string>Recipe</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>recipe</string>
</array>
</dict>
</dict>
</array>
```
**Both are required.** The Swift declaration alone makes it compile, but cross-app transfers silently fail without the Info.plist entry.
### Imported vs Exported Types
- **Exported** (`exportedAs:`) — Your app owns this type. Use for app-specific formats.
- **Imported** (`importedAs:`) — Another app owns this type. Use when you want to accept their format.
### UTType Conformance
Custom types should conform to system types for broader compatibility:
```swift
// Your .recipe conforms to public.data (binary data)
// This means any receiver that accepts generic data can also accept recipes
```
Common conformance parents: `public.data`, `public.content`, `public.text`, `public.image`
---
## Part 5: UIKit Bridging
### NSItemProvider + Transferable
Bridge between UIKit's `NSItemProvider` (used by `UIActivityViewController`, extensions, drag sessions) and `Transferable`:
```swift
// Load a Transferable from an NSItemProvider
let provider: NSItemProvider = // from drag session, extension, etc.
provider.loadTransferable(type: Profile.self) { result in
switch result {
case .success(let profile):
// Use the profile
case .failure(let error):
// Handle error
}
}
```
### When to Use UIActivityViewController
`ShareLink` covers most sharing needs. Use `UIActivityViewController` when you need:
- Custom activity items or excluded activity types
- `UIActivityItemsConfiguration` for lazy item provision
- Custom `UIActivity` subclasses
- Programmatic presentation control
```swift
struct ShareSheet: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}
```
For most apps, `ShareLink` is sufficient and preferred — it integrates with `Transferable` natively.
---
## Part 6: Gotchas & Troubleshooting
### FileRepresentation Temporary File Lifecycle
The `received.file` URL in a `FileRepresentation` importing closure has a temporary sandbox extension. The system may revoke access after the closure returns. Always copy the file:
```swift
// WRONG — file may become inaccessible
return Video(file: received.file)
// RIGHT — copy to your own storage
let dest = myAppDirectory.appendingPathComponent(received.file.lastPathComponent)
try FileManager.default.copyItem(at: received.file, to: dest)
return Video(file: dest)
```
### Async Work After File Drop
The `FileRepresentation` importing closure is synchronous — you cannot `await` inside it. Copy the file first, return the model, then do async post-processing (thumbnails, transcoding, metadata extraction) on the copied URL:
```swift
// WRONG — can't await in the importing closure
FileRepresentation(importedContentType: .movie) { received in
let dest = ...
try FileManager.default.copyItem(at: received.file, to: dest)
let thumbnail = await generateThumbnail(for: dest) // ❌ compile error
return VideoClip(localURL: dest, thumbnail: thumbnail)
}
// RIGHT — return immediately, process async afterward
// In your view model or drop handler:
.dropDestination(for: VideoClip.self) { clips, _ in
for clip in clips {
timeline.append(clip)
Task {
// clip.localURL is the COPY — safe to access anytime
let thumbnail = await generateThumbnail(for: clip.localURL)
clip.thumbnail = thumbnail
}
}
return true
}
```
### Representation Ordering
Representations are tried **in declaration order**. The receiver uses the first one it supports.
```swift
// WRONG — receivers always get plain text
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.name) // ← every receiver supports String
CodableRepresentation(contentType: .profile) // ← never reached
}
// RIGHT — richest first, fallbacks last
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile) // ← apps that understand Profile
ProxyRepresentation(exporting: \.name) // ← fallback for everyone else
}
```
### Custom UTType Without Info.plist
If you declare `UTType(exportedAs: "com.myapp.type")` in Swift but forget the Info.plist entry:
- In-app transfers work (same process recognizes the type)
- Cross-app transfers silently fail (other apps can't resolve the type)
This is the most common "works in development, fails in production" issue.
### Drop Target Hit Testing
`.dropDestination` requires the view to have a non-zero frame for hit testing. If drops aren't registering:
```swift
// WRONG — Color.clear has zero intrinsic size
Color.clear
.dropDestination(for: Image.self) { ... }
// RIGHT — give it a frame
Color.clear
.frame(width: 200, height: 200)
.contentShape(Rectangle()) // ensure full area is hit-testable
.dropDestination(for: Image.self) { ... }
```
### Async Loading with loadTransferable
`NSItemProvider.loadTransferable` is asynchronous. Update UI on the main actor:
```swift
provider.loadTransferable(type: Profile.self) { result in
Task { @MainActor in
switch result {
case .success(let profile):
self.profile = profile
case .failure(let error):
self.errorMessage = error.localizedDescription
}
}
}
```
### PasteButton Platform Differences
`PasteButton` auto-validates against pasteboard changes on iOS — the button enables/disables as the pasteboard content changes. On macOS, this automatic validation does not occur. If your macOS app needs dynamic paste validation, monitor `UIPasteboard.changedNotification` (UIKit) or `NSPasteboard` change count manually.
---
## Resources
**WWDC**: 2022-10062, 2022-10052, 2022-10023, 2022-10093, 2022-10095
**Docs**: /coretransferable/transferable, /coretransferable/choosing-a-transfer-representation-for-a-model-type, /coretransferable/filerepresentation, /coretransferable/proxyrepresentation, /swiftui/sharelink, /swiftui/drag-and-drop, /swiftui/clipboard, /uniformtypeidentifiers
**Skills**: axiom-photo-library, axiom-codable, axiom-swiftui-gestures, axiom-app-intents-ref
COMPATIBILITY & REFERENCES
Platform guidance
Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.
Creates comprehensive frontend UI/UX designs including user interfaces, design systems, component libraries, responsive layouts, and accessibility implementations. Produces wireframes, mockups, design specifications, and implementation guidelines. Use when designing user interfaces, creating desi...
Expert in building scalable design systems with Atomic Design, design tokens, and theming infrastructure. Use when creating component libraries, implementing dark mode, or establishing typography and color systems. Covers multi-brand support, Storybook-driven development, and accessibility-first ...
Use when implementing Liquid Glass effects, reviewing UI for Liquid Glass adoption, debugging visual artifacts, optimizing performance, or requesting expert review of Liquid Glass implementation - provides comprehensive design principles, API patterns, and troubleshooting guidance from WWDC 2025....