onDrop 是 SwiftUI 中用于处理拖放操作(Drag & Drop)的视图修饰符,它允许让某个视图变成“拖入目标”,接收用户从别处拖进来的文件、图片、文本、URL 等数据。
基本介绍
onDrop代码:
func onDrop(of supportedTypes: [UTType], isTargeted: Binding<Bool>? = nil, perform: @escaping ([NSItemProvider]) -> Bool) -> some View
作用:使视图变成拖入目标(Drop Target)
平台:macOS / iPadOS / iOS(支持多平台)
类型安全:支持使用 UTType(如 .fileURL, .image, .plainText)
基本用途
1、拖入文件:拖动文件到窗口中。
2、拖入图片:拖动图片到 ImageView 中显示。
3、拖入文本:拖动文本更新文本框。
4、拖入自定义数据:通过 NSItemProvider 解码模型。
常用格式
常用的onDrop格式、参数:
.onDrop(of: [UTType.fileURL], isTargeted: $isHovering) { providers in
// 处理 NSItemProvider 列表
return true // 返回是否接受了拖入内容
}
参数说明:
1、of:支持的拖入类型,使用 [UTType]。
2、isTargeted:Binding绑定变量,在拖入悬停时自动设为 true,可用于 UI 高亮。
Text(filePath ?? "拖入文件")
.frame(width: 300, height: 200)
.background(isTargeted ? Color.blue.opacity(0.3) : Color.gray.opacity(0.2))
.onDrop()
当拖动文件到该文本上时,isTargeted自动变为true,背景色会根据isTargeted进行调整。
3、perform:拖放时调用的回调函数,传入 NSItemProvider 列表
使用场景
1、拖入文件URL
import SwiftUI
import UniformTypeIdentifiers
struct DropFileView: View {
@State private var isTargeted = false
@State private var filePath: String?
var body: some View {
VStack {
Text(filePath ?? "拖入文件")
.frame(width: 300, height: 200)
.background(isTargeted ? Color.blue.opacity(0.3) : Color.gray.opacity(0.2))
.cornerRadius(12)
.onDrop(of: [UTType.fileURL], isTargeted: $isTargeted) { providers in
if let provider = providers.first {
provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { data, _ in
DispatchQueue.main.async {
if let data = data as? Data,
let url = URL(dataRepresentation: data, relativeTo: nil) {
filePath = url.path
}
}
}
return true
}
return false
}
}
}
}

2、拖入图片
.onDrop(of: [.image]) { providers in
providers.first?.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, _ in
if let data = data, let nsImage = NSImage(data: data) {
DispatchQueue.main.async {
self.image = nsImage
}
}
}
return true
}
支持类型
1、.fileURL:文件路径(如访达中拖文件)。
.onDrop(of: [.fileURL], isTargeted: $isTargeted) { providers in
for provider in providers {
provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { (data, _) in
if let data = data as? Data,
let url = URL(dataRepresentation: data, relativeTo: nil) {
DispatchQueue.main.async {
print("拖入文件路径:\(url.path)")
// 你可以加载文件,或者保存路径
}
}
}
}
return true
}
2、.image:图片(PNG、JPEG)。
.onDrop(of: [.image], isTargeted: $isTargeted) { providers in
images = []
for provider in providers {
provider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, _ in
if let data = data, let nsImage = NSImage(data: data) {
DispatchQueue.main.async {
self.images.append(nsImage)
}
}
}
}
return true
}
3、.plainText:文本。
.onDrop(of: [.plainText], isTargeted: $isTargeted) { providers in
for provider in providers {
provider.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { (data, _) in
if let data = data as? Data,
let text = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
print("拖入文本:\(text)")
}
}
}
}
return true
}
4、.url:网址(Safari拖出)。
.onDrop(of: [.url], isTargeted: $isTargeted) { providers in
for provider in providers {
provider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { (data, _) in
if let data = data as? Data,
let url = URL(dataRepresentation: data, relativeTo: nil) {
DispatchQueue.main.async {
print("拖入 URL:\(url)")
}
}
}
}
return true
}
5、.pdf:PDF 文档。
.onDrop(of: [.pdf], isTargeted: $isTargeted) { providers in
for provider in providers {
provider.loadDataRepresentation(forTypeIdentifier: UTType.pdf.identifier) { data, _ in
if let data = data {
DispatchQueue.main.async {
let pdf = NSPDFImageRep(data: data)
print("拖入 PDF 页数:\(pdf?.pageCount ?? 0)")
}
}
}
}
return true
}
6、.movie:视频。
.onDrop(of: [.movie], isTargeted: $isTargeted) { providers in
for provider in providers {
provider.loadItem(forTypeIdentifier: UTType.movie.identifier, options: nil) { (data, _) in
if let data = data as? Data {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("temp.mov")
try? data.write(to: tempURL)
DispatchQueue.main.async {
print("拖入视频保存到:\(tempURL)")
}
}
}
}
return true
}
7、.data:原始数据。
.onDrop(of: [.data], isTargeted: $isTargeted) { providers in
for provider in providers {
provider.loadDataRepresentation(forTypeIdentifier: UTType.data.identifier) { data, _ in
if let data = data {
DispatchQueue.main.async {
print("拖入原始数据大小:\(data.count)")
}
}
}
}
return true
}
扩展知识
1、如何判断是否拖入区域?
通过绑定 @State var isTargeted 实现:
@State private var isTargeted = false
.onDrop(of: ..., isTargeted: $isTargeted) { ... }
.background(isTargeted ? Color.blue : Color.clear)
2、为什么onDrop必须return true?
SwiftUI 的 onDrop 要求在 closure 的结尾返回 Bool,用于告诉系统:
true:成功接受了拖入的数据(处理了)。
false:拒绝拖入的数据(没处理)。
如果return false,即使读取成功,那么系统会认为“这次拖放成功” → 无高亮动画,无释放动画。

3、上传多个图片
struct ContentView: View {
@State private var isTargeted = true
@State private var images: [NSImage] = []
var body: some View {
if !images.isEmpty {
ForEach(images, id: \.self) { img in
Image(nsImage: img)
.resizable()
.scaledToFit()
.frame(width: 300,height:200)
}
} else {
Text("没有照破")
}
Text("拖入文件")
.frame(width: 300, height: 200)
.background(isTargeted ? Color.blue.opacity(0.3) : Color.gray.opacity(0.2))
.cornerRadius(12)
.onDrop(of: [.image],isTargeted: $isTargeted) { providers in
images = []
for provider in providers {
provider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, _ in
if let data = data, let nsImage = NSImage(data: data) {
DispatchQueue.main.async {
self.images.append(nsImage)
}
}
}
}
return true
}
}
}

4、背后原理
SwiftUI 的 onDrop 封装了 AppKit 的 NSDraggingDestination(macOS)或 UIKit 的 UIDropInteraction(iOS)。
实际上传入的数据是 NSItemProvider,可以通过它异步读取拖入的数据内容。
多平台统一接口,支持大部分常见数据类型。
常见问题
1、onDrop 不触发:没有指定正确的 UTType。
2、数据加载失败:没有在主线程中更新 UI。
3、多个 providers 忽略:providers.first 只处理一个拖入项。
4、拖入后没有 UI 提示:忘记绑定 isTargeted。
总结
onDrop支持跨平台,适用于macOS、ipadOS和iOS,支持文本、图片、文件等类型。