重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
axiom-transferable-ref by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-transferable-ref关于 CoreTransferable 框架和 SwiftUI 共享界面的全面指南:拖放、复制/粘贴以及 ShareLink。
.draggable、.dropDestination).copyable、.pasteDestination、PasteButton)ShareLink 共享内容Transferable 类型与 UIKit 的 NSItemProvider 桥接CodableRepresentation、、 和 之间进行选择广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
DataRepresentationFileRepresentationProxyRepresentation"如何在 SwiftUI 中使我的模型可拖动?" "ShareLink 没有显示我的自定义预览" "如何在视图中接受拖放的文件?" "DataRepresentation 和 FileRepresentation 有什么区别?" "如何为我的自定义类型添加复制/粘贴支持?" "我的拖放在应用内有效,但在跨应用时无效" "如何声明自定义 UTType?"
你的模型类型...
├─ 遵循 Codable 且不需要特定的二进制格式?
│ → CodableRepresentation
├─ 具有自定义二进制格式(内存中的 Data)?
│ → DataRepresentation(导出/导入闭包)
├─ 存在于磁盘上(大文件、视频、文档)?
│ → FileRepresentation(传递文件 URL,而非字节)
├─ 需要为不理解你类型的接收方提供回退方案?
│ → 添加 ProxyRepresentation(例如,导出为 String 或 URL)
└─ 需要根据条件隐藏某个表示?
→ 对任何表示应用 .exportingCondition
| 错误 / 症状 | 原因 | 修复方法 |
|---|---|---|
| "Type does not conform to Transferable" | 缺少 transferRepresentation | 添加 static var transferRepresentation: some TransferRepresentation |
| 拖放应用内有效但跨应用无效 | 未在 Info.plist 中声明自定义 UTType | 添加 UTExportedTypeDeclarations 条目 |
| 接收方总是收到纯文本而非富类型 | ProxyRepresentation 排在 CodableRepresentation 之前 | 重新排序:最丰富的表示优先 |
| FileRepresentation 因"文件未找到"而崩溃 | 接收方未在沙盒扩展过期前复制文件 | 在导入闭包中将文件复制到应用存储 |
| PasteButton 始终禁用 | 粘贴板不包含匹配的 Transferable 类型 | 检查 UTType 遵循性;验证粘贴的数据是否匹配 |
| ShareLink 显示通用预览 | 未提供 SharePreview 或图像不是 Transferable | 提供带有标题和图像的显式 SharePreview |
.dropDestination 闭包从未触发 | 负载类型错误或视图命中测试区域为零 | 验证 for: 类型是否与拖动内容匹配;添加 .frame() 或 .contentShape() |
这些类型无需额外代码即可工作——无需遵循协议:
String、Data、URL、AttributedString、Image、Color
Transferable 协议有一个要求:一个静态的 transferRepresentation 属性。
最佳适用场景:已遵循 Codable 的模型。默认使用 JSON。
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)
}
}
自定义编码器/解码器(例如,使用 PropertyList 而非 JSON):
CodableRepresentation(
contentType: .todo,
encoder: PropertyListEncoder(),
decoder: PropertyListDecoder()
)
要求:自定义 UTType 需要在 Info.plist 中有匹配的 UTExportedTypeDeclarations(见第四部分)。
最佳适用场景:自定义二进制格式,其中数据在内存中且你控制序列化。
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)
}
}
}
仅导入或仅导出变体:
// 仅导入
DataRepresentation(importedContentType: .png) { data in
try MyImage(pngData: data)
}
// 仅导出
DataRepresentation(exportedContentType: .png) { image in
try image.pngData()
}
避免使用 UTType.data 作为内容类型——使用特定类型如 .png、.pdf、.commaSeparatedText。
最佳适用场景:磁盘上的大负载(视频、文档、归档文件)。传递文件 URL 而不是将字节加载到内存中。
struct Video: Transferable {
let file: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// 必须复制——沙盒扩展是临时的
let dest = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mp4")
try FileManager.default.copyItem(at: received.file, to: dest)
return Video(file: dest)
}
}
}
关键:received.file URL 具有临时的沙盒扩展。在导入闭包中将文件复制到你自己的存储中——该 URL 在闭包返回后将无法访问。
SentTransferredFile 属性:
file: URL — 文件位置allowAccessingOriginalFile: Bool — 当为 false(默认)时,接收方获得一个副本ReceivedTransferredFile 属性:
file: URL — 磁盘上接收到的文件isOriginalFile: Bool — 这是发送方的原始文件还是副本内容类型精度:.mpeg4Movie 仅匹配 .mp4 文件。要接受所有常见视频格式(.mp4、.mov、.m4v),请使用父类型 .movie——或者为特定子类型声明多个 FileRepresentation:
// 宽泛:接受系统识别的任何视频格式
FileRepresentation(contentType: .movie) { ... } importing: { ... }
// 或具体:每种格式单独的处理程序
FileRepresentation(contentType: .mpeg4Movie) { ... } importing: { ... }
FileRepresentation(contentType: .quickTimeMovie) { ... } importing: { ... }
仅导入:当你的类型仅接收文件(放置目标,不导出)时,使用仅导入初始化器——它使意图明确并避免意外导出:
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)
}
最佳适用场景:回退表示,让你的类型能与期望更简单类型的接收方协同工作。
struct Profile: Transferable {
var name: String
var avatar: Image
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile)
ProxyRepresentation(exporting: \.name) // 回退:粘贴为文本
}
}
仅导出代理(常见模式——反向转换通常不可能):
ProxyRepresentation(exporting: \.name) // Profile → String(单向)
双向代理(当反向转换有意义时):
ProxyRepresentation { item in
item.name // 导出
} importing: { name in
Profile(name: name) // 导入
}
在 transferRepresentation 主体中列出表示。顺序很重要——接收方使用它们支持的第一个表示。
struct Profile: Transferable {
static var transferRepresentation: some TransferRepresentation {
// 1. 最丰富:完整的配置文件数据(理解 .profile 的应用)
CodableRepresentation(contentType: .profile)
// 2. 回退:纯文本(文本字段、笔记、任何应用)
ProxyRepresentation(exporting: \.name)
}
}
常见错误:将 ProxyRepresentation 放在首位会导致支持两者的接收方总是获得降级版本。
在运行时条件不满足时隐藏某个表示:
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.toCSV()
} importing: { data in
try Self(csvData: data)
}
.exportingCondition { archive in
archive.supportsCSV
}
控制哪些进程可以看到某个表示:
CodableRepresentation(contentType: .profile)
.visibility(.ownProcess) // 仅在此应用内
选项:.all(默认)、.team(同一开发者团队)、.group(同一 App Group,macOS)、.ownProcess(仅同一应用)
为写入磁盘的接收方提供提示:
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// ...
}
.suggestedFileName("My Video.mp4")
// 或动态:
.suggestedFileName { video in video.title + ".mp4" }
标准的共享入口点。接受任何 Transferable 类型。
// 简单:共享一个字符串
ShareLink(item: "Check out this app!")
// 带预览
ShareLink(
item: photo,
preview: SharePreview(photo.caption, image: photo.image)
)
// 共享一个 URL 并带有自定义预览(防止系统获取元数据)
ShareLink(
item: URL(string: "https://example.com")!,
preview: SharePreview("My Site", image: Image("hero"))
)
共享多个项目,每个项目都有预览:
ShareLink(items: photos) { photo in
SharePreview(photo.caption, image: photo.image)
}
SharePreview 初始化器:
SharePreview("Title") — 仅文本SharePreview("Title", image: someImage) — 文本 + 全尺寸图像SharePreview("Title", icon: someIcon) — 文本 + 缩略图图标SharePreview("Title", image: someImage, icon: someIcon) — 三者皆有注意:如果为自定义类型省略 SharePreview,共享表单将显示通用预览。对于非平凡类型,请始终提供一个。
使视图可拖动:
Text(profile.name)
.draggable(profile)
带有自定义拖动预览:
Text(profile.name)
.draggable(profile) {
Label(profile.name, systemImage: "person")
.padding()
.background(.regularMaterial)
}
接受放置:
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
}
多种项目类型——使用遵循 Transferable 的枚举包装器,而不是堆叠 .dropDestination 修饰符(堆叠可能导致只有最外层的处理程序触发):
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——与 .onMove 结合使用,或使用 draggable/dropDestination 进行跨容器移动。
复制支持(激活编辑 > 复制 / Cmd+C):
List(items) { item in
Text(item.name)
}
.copyable(items)
粘贴支持(激活编辑 > 粘贴 / Cmd+V):
List(items) { item in
Text(item.name)
}
.pasteDestination(for: Item.self) { pasted in
items.append(contentsOf: pasted)
} validator: { candidates in
candidates.filter { $0.isValid }
}
验证器闭包在操作之前运行——返回空数组以阻止粘贴。
剪切支持:
.cuttable(for: Item.self) {
let selected = items.filter { $0.isSelected }
items.removeAll { $0.isSelected }
return selected
}
PasteButton——处理粘贴并带有类型过滤的系统按钮:
PasteButton(payloadType: String.self) { strings in
notes.append(contentsOf: strings)
}
平台差异:PasteButton 在 iOS 上自动验证粘贴板更改,但在 macOS 上不会。
可用性:.copyable、.pasteDestination 和 .cuttable 仅限 macOS 13+——它们在 iOS 上不存在。在 iOS 上,使用 PasteButton(iOS 16+)进行粘贴,并使用标准上下文菜单或 UIPasteboard 进行程序化复制/剪切。PasteButton 是跨平台的:macOS 10.15+、iOS 16+、visionOS 1.0+。
尽可能使用 Apple 的内置 UTType——它们已经在整个系统中被识别:
import UniformTypeIdentifiers
// 常见类型
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
步骤 1:在 Swift 中声明:
extension UTType {
static var recipe: UTType = UTType(exportedAs: "com.myapp.recipe")
}
步骤 2:添加到 Info.plist 中的 UTExportedTypeDeclarations 下:
<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>
两者都是必需的。 仅 Swift 声明使其可以编译,但没有 Info.plist 条目,跨应用传输会静默失败。
exportedAs:)—— 你的应用拥有此类型。用于应用特定格式。importedAs:)—— 另一个应用拥有此类型。当你想接受它们的格式时使用。自定义类型应遵循系统类型以获得更广泛的兼容性:
// 你的 .recipe 遵循 public.data(二进制数据)
// 这意味着任何接受通用数据的接收方也可以接受 recipe
常见的遵循父类型:public.data、public.content、public.text、public.image
在 UIKit 的 NSItemProvider(由 UIActivityViewController、扩展、拖动会话使用)和 Transferable 之间桥接:
// 从 NSItemProvider 加载 Transferable
let provider: NSItemProvider = // 来自拖动会话、扩展等
provider.loadTransferable(type: Profile.self) { result in
switch result {
case .success(let profile):
// 使用 profile
case .failure(let error):
// 处理错误
}
}
ShareLink 涵盖了大多数共享需求。当你需要以下功能时使用 UIActivityViewController:
自定义活动项目或排除的活动类型
UIActivityItemsConfiguration 用于延迟提供项目
自定义 UIActivity 子类
程序化呈现控制
struct ShareSheet: UIViewControllerRepresentable { let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}
对于大多数应用,ShareLink 是足够且首选的方式——它原生集成了 Transferable。
FileRepresentation 导入闭包中的 received.file URL 具有临时的沙盒扩展。系统可能在闭包返回后撤销访问权限。始终复制文件:
// 错误——文件可能变得无法访问
return Video(file: received.file)
// 正确——复制到你自己的存储
let dest = myAppDirectory.appendingPathComponent(received.file.lastPathComponent)
try FileManager.default.copyItem(at: received.file, to: dest)
return Video(file: dest)
FileRepresentation 导入闭包是同步的——你不能在其中使用 await。先复制文件,返回模型,然后在复制的 URL 上执行异步后处理(缩略图、转码、元数据提取):
// 错误——不能在导入闭包中 await
FileRepresentation(importedContentType: .movie) { received in
let dest = ...
try FileManager.default.copyItem(at: received.file, to: dest)
let thumbnail = await generateThumbnail(for: dest) // ❌ 编译错误
return VideoClip(localURL: dest, thumbnail: thumbnail)
}
// 正确——立即返回,之后异步处理
// 在你的视图模型或放置处理程序中:
.dropDestination(for: VideoClip.self) { clips, _ in
for clip in clips {
timeline.append(clip)
Task {
// clip.localURL 是副本——可随时安全访问
let thumbnail = await generateThumbnail(for: clip.localURL)
clip.thumbnail = thumbnail
}
}
return true
}
表示按声明顺序尝试。接收方使用它支持的第一个。
// 错误——接收方总是获得纯文本
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.name) // ← 每个接收方都支持 String
CodableRepresentation(contentType: .profile) // ← 永远不会到达
}
// 正确——最丰富的优先,回退最后
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile) // ← 理解 Profile 的应用
ProxyRepresentation(exporting: \.name) // ← 其他所有人的回退
}
如果你在 Swift 中声明了 UTType(exportedAs: "com.myapp.type") 但忘记了 Info.plist 条目:
这是最常见的"开发中有效,生产中失败"的问题。
.dropDestination 要求视图具有非零帧以进行命中测试。如果放置未注册:
// 错误——Color.clear 具有零固有尺寸
Color.clear
.dropDestination(for: Image.self) { ... }
// 正确——给它一个帧
Color.clear
.frame(width: 200, height: 200)
.contentShape(Rectangle()) // 确保整个区域可进行命中测试
.dropDestination(for: Image.self) { ... }
NSItemProvider.loadTransferable 是异步的。在主执行器上更新 UI:
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 在 iOS 上自动验证粘贴板更改——按钮会随着粘贴板内容的变化而启用/禁用。在 macOS 上,这种自动验证不会发生。如果你的 macOS 应用需要动态粘贴验证,请手动监控 UIPasteboard.changedNotification(UIKit)或 NSPasteboard 更改计数。
WWDC:2022-10062、2022-10052、2022-10023、2022-10093、2022-10095
文档:/coretransferable/transferable、/coretransferable/choosing-a-transfer-representation-for-a-model-type、/coretransferable/filerepresentation、/coretransferable/proxyrepresentation、/swiftui/sharelink、/swiftui/drag-and-drop、/swiftui/clipboard、/uniformtypeidentifiers
技能:axiom-photo-library、axiom-codable、axiom-swiftui-gestures、axiom-app-intents-ref
每周安装数
36
代码库
GitHub 星标数
590
首次出现
2026年2月21日
安全审计
安装于
codex35
opencode34
github-copilot34
kimi-cli34
gemini-cli34
amp34
Comprehensive guide to the CoreTransferable framework and SwiftUI sharing surfaces: drag and drop, copy/paste, and ShareLink.
.draggable, .dropDestination).copyable, .pasteDestination, PasteButton)ShareLinkTransferable types with UIKit's NSItemProviderCodableRepresentation, DataRepresentation, FileRepresentation, and ProxyRepresentation"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?"
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
| 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 |
These work with zero additional code — no conformance needed:
String, Data, URL, AttributedString, Image, Color
The Transferable protocol has one requirement: a static transferRepresentation property.
Best for: models already conforming to Codable. Uses JSON by default.
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):
CodableRepresentation(
contentType: .todo,
encoder: PropertyListEncoder(),
decoder: PropertyListDecoder()
)
Requirement : Custom UTTypes need matching UTExportedTypeDeclarations in Info.plist (see Part 4).
Best for: custom binary formats where data is in memory and you control serialization.
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:
// 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.
Best for: large payloads on disk (videos, documents, archives). Passes file URLs instead of loading bytes into memory.
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 locationallowAccessingOriginalFile: Bool — when false (default), receiver gets a copyReceivedTransferredFile properties:
file: URL — the received file on diskisOriginalFile: Bool — whether this is the sender's original file or a copyContent type precision : .mpeg4Movie only matches .mp4 files. To accept all common video formats (.mp4, .mov, .m4v), use the parent type .movie — or declare multiple FileRepresentations for specific subtypes:
// 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:
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)
}
Best for: fallback representations that let your type work with receivers expecting simpler types.
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):
ProxyRepresentation(exporting: \.name) // Profile → String (one-way)
Bidirectional proxy (when reverse makes sense):
ProxyRepresentation { item in
item.name // export
} importing: { name in
Profile(name: name) // import
}
List representations in the transferRepresentation body. Order matters — receivers use the first representation they support.
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.
Hide a representation at runtime when conditions aren't met:
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.toCSV()
} importing: { data in
try Self(csvData: data)
}
.exportingCondition { archive in
archive.supportsCSV
}
Control which processes can see a representation:
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)
Hint for receivers writing to disk:
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// ...
}
.suggestedFileName("My Video.mp4")
// Or dynamic:
.suggestedFileName { video in video.title + ".mp4" }
The standard sharing entry point. Accepts any Transferable type.
// 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:
ShareLink(items: photos) { photo in
SharePreview(photo.caption, image: photo.image)
}
SharePreview initializers:
SharePreview("Title") — text onlySharePreview("Title", image: someImage) — text + full-size imageSharePreview("Title", icon: someIcon) — text + thumbnail iconSharePreview("Title", image: someImage, icon: someIcon) — all threeGotcha : If you omit SharePreview for a custom type, the share sheet shows a generic preview. Always provide one for non-trivial types.
Making a view draggable:
Text(profile.name)
.draggable(profile)
With custom drag preview:
Text(profile.name)
.draggable(profile) {
Label(profile.name, systemImage: "person")
.padding()
.background(.regularMaterial)
}
Accepting drops:
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):
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.
Copy support (activates Edit > Copy / Cmd+C):
List(items) { item in
Text(item.name)
}
.copyable(items)
Paste support (activates Edit > Paste / Cmd+V):
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:
.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:
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+.
Use Apple's built-in UTTypes when possible — they're already recognized across the system:
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
Step 1 : Declare in Swift:
extension UTType {
static var recipe: UTType = UTType(exportedAs: "com.myapp.recipe")
}
Step 2 : Add to Info.plist under UTExportedTypeDeclarations:
<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.
exportedAs:) — Your app owns this type. Use for app-specific formats.importedAs:) — Another app owns this type. Use when you want to accept their format.Custom types should conform to system types for broader compatibility:
// 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
Bridge between UIKit's NSItemProvider (used by UIActivityViewController, extensions, drag sessions) and Transferable:
// 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
}
}
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
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.
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:
// 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)
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:
// 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
}
Representations are tried in declaration order. The receiver uses the first one it supports.
// 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
}
If you declare UTType(exportedAs: "com.myapp.type") in Swift but forget the Info.plist entry:
This is the most common "works in development, fails in production" issue.
.dropDestination requires the view to have a non-zero frame for hit testing. If drops aren't registering:
// 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) { ... }
NSItemProvider.loadTransferable is asynchronous. Update UI on the main actor:
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 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.
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
Weekly Installs
36
Repository
GitHub Stars
590
First Seen
Feb 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex35
opencode34
github-copilot34
kimi-cli34
gemini-cli34
amp34
| 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() |