axiom-transferable-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTransferable & Content Sharing Reference
Transferable 与内容分享参考指南
Comprehensive guide to the CoreTransferable framework and SwiftUI sharing surfaces: drag and drop, copy/paste, and ShareLink.
本文是关于CoreTransferable框架和SwiftUI分享交互组件的综合指南:包括拖拽、复制粘贴和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 types with UIKit's
TransferableNSItemProvider - Choosing between ,
CodableRepresentation,DataRepresentation, andFileRepresentationProxyRepresentation
- 实现拖拽功能(、
.draggable).dropDestination - 添加复制粘贴支持(、
.copyable、.pasteDestination)PasteButton - 通过分享内容
ShareLink - 让自定义类型支持Transferable
- 为应用专属格式声明自定义UTType
- 将Transferable类型与UIKit的进行桥接
NSItemProvider - 在、
CodableRepresentation、DataRepresentation和FileRepresentation之间做选择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?"
"如何让我的模型在SwiftUI中支持拖拽?"
"ShareLink没有显示我的自定义预览"
"如何在我的视图中接收拖入的文件?"
"DataRepresentation和FileRepresentation有什么区别?"
"如何为我的自定义类型添加复制粘贴支持?"
"我的拖拽功能在应用内有效,但跨应用无效"
"如何声明自定义UTType?"
Part 1: Quick Reference
第一部分:快速参考
Decision Tree: Which TransferRepresentation?
决策树:选择哪种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 representationYour 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 representationCommon Errors
常见错误
| Error / Symptom | Cause | Fix |
|---|---|---|
| "Type does not conform to Transferable" | Missing | Add |
| Drop works in-app but not across apps | Custom UTType not declared in Info.plist | Add |
| 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 | Supply explicit |
| Wrong payload type or view has zero hit-test area | Verify |
| 错误/症状 | 原因 | 解决方法 |
|---|---|---|
| "Type does not conform to Transferable" | 缺少 | 添加 |
| 拖拽在应用内有效,但跨应用无效 | 自定义UTType未在Info.plist中声明 | 添加 |
| 接收方总是获取纯文本而非富类型 | ProxyRepresentation在CodableRepresentation之前声明 | 调整顺序:先声明最丰富的表示类型 |
| FileRepresentation崩溃,提示"file not found" | 接收方在沙箱扩展过期前未复制文件 | 在导入闭包中将文件复制到应用存储目录 |
| PasteButton始终处于禁用状态 | 剪贴板中没有匹配的Transferable类型 | 检查UTType一致性;验证粘贴的数据是否匹配 |
| ShareLink显示通用预览 | 未提供 | 提供包含标题和图片的显式 |
| 负载类型错误或视图点击测试区域为零 | 验证 |
Built-in Transferable Types
内置Transferable类型
These work with zero additional code — no conformance needed:
StringDataURLAttributedStringImageColor这些类型无需额外代码即可使用——无需实现协议:
StringDataURLAttributedStringImageColorPart 2: Making Types Transferable
第二部分:让类型支持Transferable
The protocol has one requirement: a static property.
TransferabletransferRepresentationTransferabletransferRepresentationCodableRepresentation
CodableRepresentation
Best for: models already conforming to . Uses JSON by default.
Codableswift
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 in Info.plist (see Part 4).
UTExportedTypeDeclarations最佳适用场景:已遵循的模型。默认使用JSON格式。
Codableswift
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):
swift
CodableRepresentation(
contentType: .todo,
encoder: PropertyListEncoder(),
decoder: PropertyListDecoder()
)注意:自定义UTType需要在Info.plist中添加对应的(见第四部分)。
UTExportedTypeDeclarationsDataRepresentation
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 as the content type — use a specific type like , , .
UTType.data.png.pdf.commaSeparatedText最佳适用场景:数据在内存中且你控制序列化逻辑的自定义二进制格式。
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)
}
}
}仅导入或仅导出的变体:
swift
// 仅导入
DataRepresentation(importedContentType: .png) { data in
try MyImage(pngData: data)
}
// 仅导出
DataRepresentation(exportedContentType: .png) { image in
try image.pngData()
}避免使用作为内容类型——使用特定类型如、、。
UTType.data.png.pdf.commaSeparatedTextFileRepresentation
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 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.
received.fileSentTransferredFile- — the file location
file: URL - — when
allowAccessingOriginalFile: Bool(default), receiver gets a copyfalse
ReceivedTransferredFile- — the received file on disk
file: URL - — whether this is the sender's original file or a copy
isOriginalFile: Bool
Content type precision: only matches files. To accept all common video formats (, , ), use the parent type — or declare multiple s for specific subtypes:
.mpeg4Movie.mp4.mp4.mov.m4v.movieFileRepresentationswift
// 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)
}最佳适用场景:磁盘上的大负载(视频、文档、归档文件)。传递文件URL而非字节数据。
swift
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)
}
}
}关键提示: URL拥有临时沙箱扩展。系统可能在闭包返回后撤销访问权限。务必在导入闭包中将文件复制到你自己的存储目录。
received.fileSentTransferredFile- — 文件路径
file: URL - — 当为
allowAccessingOriginalFile: Bool(默认)时,接收方会获取文件副本false
ReceivedTransferredFile- — 磁盘上的接收文件
file: URL - — 这是发送方的原始文件还是副本
isOriginalFile: Bool
内容类型精度:仅匹配文件。要接受所有常见视频格式(、、),请使用父类型——或者为特定子类型声明多个:
.mpeg4Movie.mp4.mp4.mov.m4v.movieFileRepresentationswift
// 宽泛:接受系统识别的任何视频格式
FileRepresentation(contentType: .movie) { ... } importing: { ... }
// 或特定:为每种格式单独处理
FileRepresentation(contentType: .mpeg4Movie) { ... } importing: { ... }
FileRepresentation(contentType: .quickTimeMovie) { ... } importing: { ... }仅导入:当你的类型仅接收文件(作为拖放目标,不导出)时,使用仅导入的初始化器——它能明确表达意图并避免意外导出:
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
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
}最佳适用场景:提供降级表示,让你的类型能与期望更简单类型的接收方兼容。
swift
struct Profile: Transferable {
var name: String
var avatar: Image
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile)
ProxyRepresentation(exporting: \.name) // 降级方案:作为文本粘贴
}
}仅导出的代理(常见模式——反向转换通常不可能):
swift
ProxyRepresentation(exporting: \.name) // Profile → String(单向)双向代理(当反向转换有意义时):
swift
ProxyRepresentation { item in
item.name // 导出
} importing: { name in
Profile(name: name) // 导入
}Combining Multiple Representations
组合多种表示类型
List representations in the body. Order matters — receivers use the first representation they support.
transferRepresentationswift
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 first causes receivers that support both to always get the degraded version.
ProxyRepresentation在体中列出表示类型。顺序很重要——接收方会使用第一个它支持的表示类型。
transferRepresentationswift
struct Profile: Transferable {
static var transferRepresentation: some TransferRepresentation {
// 1. 最丰富:完整的Profile数据(支持.profile的应用)
CodableRepresentation(contentType: .profile)
// 2. 降级方案:纯文本(文本框、笔记应用等任何应用)
ProxyRepresentation(exporting: \.name)
}
}常见错误:把放在最前面会导致同时支持两种类型的接收方总是获取降级版本。
ProxyRepresentationConditional 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
}在运行时当条件不满足时隐藏某个表示类型:
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 appOptions: (default), (same developer team), (same App Group, macOS), (same app only)
.all.team.group.ownProcess控制哪些进程可以看到某个表示类型:
swift
CodableRepresentation(contentType: .profile)
.visibility(.ownProcess) // 仅在当前应用内可见选项:(默认)、(同一开发者团队)、(同一App Group,macOS)、(仅当前应用)
.all.team.group.ownProcessSuggested 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" }为接收方写入磁盘时提供提示:
swift
FileRepresentation(contentType: .mpeg4Movie) { video in
SentTransferredFile(video.file)
} importing: { received in
// ...
}
.suggestedFileName("My Video.mp4")
// 或动态生成:
.suggestedFileName { video in video.title + ".mp4" }Part 3: SwiftUI Surfaces
第三部分:SwiftUI交互组件
ShareLink
ShareLink
The standard sharing entry point. Accepts any type.
Transferableswift
// 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- — text only
SharePreview("Title") - — text + full-size image
SharePreview("Title", image: someImage) - — text + thumbnail icon
SharePreview("Title", icon: someIcon) - — all three
SharePreview("Title", image: someImage, icon: someIcon)
Gotcha: If you omit for a custom type, the share sheet shows a generic preview. Always provide one for non-trivial types.
SharePreview标准分享入口。接受任何类型。
Transferableswift
// 简单用法:分享字符串
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"))
)分享多个带各自预览的项目:
swift
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)
注意:如果为自定义类型省略,分享面板会显示通用预览。对于非平凡类型,务必提供。
SharePreviewSharePreviewDrag 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 rather than stacking modifiers (stacking may cause only the outermost handler to fire):
Transferable.dropDestinationswift
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 or use / for cross-container moves.
.onMovedraggabledropDestination让视图支持拖拽:
swift
Text(profile.name)
.draggable(profile)带自定义拖拽预览:
swift
Text(profile.name)
.draggable(profile) {
Label(profile.name, systemImage: "person")
.padding()
.background(.regularMaterial)
}接受拖拽:
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
}多种项目类型——使用遵循的枚举包装器,而非堆叠修饰符(堆叠可能导致仅最外层处理程序触发):
Transferable.dropDestinationswift
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重排序——与结合使用,或使用/实现跨容器移动。
.onMovedraggabledropDestinationClipboard (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: , , and are macOS 13+ only — they do not exist on iOS. On iOS, use (iOS 16+) for paste, and standard context menus or for programmatic copy/cut. is cross-platform: macOS 10.15+, iOS 16+, visionOS 1.0+.
.copyable.pasteDestination.cuttablePasteButtonUIPasteboardPasteButton复制支持(激活编辑>复制 / Cmd+C):
swift
List(items) { item in
Text(item.name)
}
.copyable(items)粘贴支持(激活编辑>粘贴 / 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 }
}验证器闭包在操作前运行——返回空数组可阻止粘贴。
剪切支持:
swift
.cuttable(for: Item.self) {
let selected = items.filter { $0.isSelected }
items.removeAll { $0.isSelected }
return selected
}PasteButton——系统按钮,处理带类型过滤的粘贴:
swift
PasteButton(payloadType: String.self) { strings in
notes.append(contentsOf: strings)
}平台差异:PasteButton在iOS上会自动验证剪贴板变化,但在macOS上不会。
可用性:、和仅支持macOS 13+——iOS上不存在这些API。在iOS上,使用(iOS 16+)实现粘贴,使用标准上下文菜单或实现程序化复制/剪切。是跨平台的:macOS 10.15+、iOS 16+、visionOS 1.0+。
.copyable.pasteDestination.cuttablePasteButtonUIPasteboardPasteButtonPart 4: UTType Declarations
第四部分:UTType声明
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尽可能使用苹果内置的UTType——它们已被系统全局识别:
swift
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-textDeclaring 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 :
UTExportedTypeDeclarationsxml
<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.
步骤1:在Swift中声明:
swift
extension UTType {
static var recipe: UTType = UTType(exportedAs: "com.myapp.recipe")
}步骤2:在Info.plist的下添加:
UTExportedTypeDeclarationsxml
<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条目,跨应用分享会静默失败。
Imported vs Exported Types
导入类型 vs 导出类型
- Exported () — Your app owns this type. Use for app-specific formats.
exportedAs: - Imported () — Another app owns this type. Use when you want to accept their format.
importedAs:
- 导出类型()——你的应用拥有该类型。用于应用专属格式。
exportedAs: - 导入类型()——其他应用拥有该类型。用于你需要接受其他应用格式的场景。
importedAs:
UTType Conformance
UTType一致性
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 recipesCommon conformance parents: , , ,
public.datapublic.contentpublic.textpublic.image自定义类型应与系统类型保持一致性以获得更广泛的兼容性:
swift
// 你的.recipe遵循public.data(二进制数据)
// 这意味着任何接受通用数据的接收方也能接收recipe常见的一致性父类型:、、、
public.datapublic.contentpublic.textpublic.imagePart 5: UIKit Bridging
第五部分:UIKit桥接
NSItemProvider + Transferable
NSItemProvider + Transferable
Bridge between UIKit's (used by , extensions, drag sessions) and :
NSItemProviderUIActivityViewControllerTransferableswift
// 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
}
}在UIKit的(被、扩展、拖拽会话使用)和之间桥接:
NSItemProviderUIActivityViewControllerTransferableswift
// 从NSItemProvider加载Transferable类型
let provider: NSItemProvider = // 来自拖拽会话、扩展等
provider.loadTransferable(type: Profile.self) { result in
switch result {
case .success(let profile):
// 使用profile
case .failure(let error):
// 处理错误
}
}When to Use UIActivityViewController
何时使用UIActivityViewController
ShareLinkUIActivityViewController- Custom activity items or excluded activity types
- for lazy item provision
UIActivityItemsConfiguration - Custom subclasses
UIActivity - 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, is sufficient and preferred — it integrates with natively.
ShareLinkTransferableShareLinkUIActivityViewController- 自定义活动项目或排除的活动类型
- 使用实现懒加载项目
UIActivityItemsConfiguration - 自定义子类
UIActivity - 程序化控制展示
swift
struct ShareSheet: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}对于大多数应用,已足够且是首选——它与原生集成。
ShareLinkTransferablePart 6: Gotchas & Troubleshooting
第六部分:常见陷阱与故障排除
FileRepresentation Temporary File Lifecycle
FileRepresentation临时文件生命周期
The URL in a importing closure has a temporary sandbox extension. The system may revoke access after the closure returns. Always copy the file:
received.fileFileRepresentationswift
// 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)FileRepresentationreceived.fileswift
// 错误——文件可能变得不可访问
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)Async Work After File Drop
文件拖放后的异步工作
The importing closure is synchronous — you cannot inside it. Copy the file first, return the model, then do async post-processing (thumbnails, transcoding, metadata extraction) on the copied URL:
FileRepresentationawaitswift
// 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
}FileRepresentationawaitswift
// 错误——不能在导入闭包中使用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
}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
}表示类型按声明顺序尝试。接收方使用第一个它支持的类型。
swift
// 错误——接收方总是获取纯文本
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.name) // ← 所有接收方都支持String
CodableRepresentation(contentType: .profile) // ← 永远不会被使用
}
// 正确——先声明最丰富的类型,最后声明降级类型
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile) // ← 支持Profile的应用
ProxyRepresentation(exporting: \.name) // ← 其他应用的降级方案
}Custom UTType Without Info.plist
缺少Info.plist条目的自定义UTType
If you declare in Swift but forget the Info.plist entry:
UTType(exportedAs: "com.myapp.type")- 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.
如果你在Swift中声明了但忘记添加Info.plist条目:
UTType(exportedAs: "com.myapp.type")- 应用内分享正常工作(同一进程能识别该类型)
- 跨应用分享会静默失败(其他应用无法解析该类型)
这是最常见的“开发环境正常,生产环境失败”问题。
Drop Target Hit Testing
拖放目标点击测试
.dropDestinationswift
// 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) { ... }.dropDestinationswift
// 错误——Color.clear没有固有尺寸
Color.clear
.dropDestination(for: Image.self) { ... }
// 正确——给视图设置尺寸
Color.clear
.frame(width: 200, height: 200)
.contentShape(Rectangle()) // 确保整个区域可被点击测试
.dropDestination(for: Image.self) { ... }Async Loading with loadTransferable
使用loadTransferable进行异步加载
NSItemProvider.loadTransferableswift
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
}
}
}NSItemProvider.loadTransferableswift
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平台差异
PasteButtonUIPasteboard.changedNotificationNSPasteboardPasteButtonUIPasteboard.changedNotificationNSPasteboardResources
资源
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
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