忘記是那一年的WWDC公布了一個拖曳相關的功能,主要是讓你可以將iPhone 的資料像是圖片之類的丟到iPad 或Mac 上,因此有了 API 可以用在 Drop & Drag 動作上。
最近因為工作需要花了一些時間在研究Drop & Drag 應該要怎麼寫,需要實作的是一般的拖拉元件改變它的順序,並不需要讓它能拖到另一個裝置上,寫作上就會比較簡單一點。
參考以下文件與官方網站來練習
Swift Practice # 177 SwiftUI 拖曳 Drop & Drag 改變陣列Array位置
Apple Documentation – Drag and Drop
Start
一開始當然要先做一個要進行拖曳的List,建立一個顯示各種茶的List
建立List 的 Item View
struct TeaItemView: View {
var title: String
var body: some View {
HStack {
Text(title)
.font(.system(size: 26))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 30)
.background(.green)
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}
再建立一個用 ForEach
做的 List
import SwiftUI
struct ContentView: View {
@State private var draggedItem: String?
@State private var fruitItems = ["🫖 Tea", " ☕️ Coffee", " 🧃 Juice", "🧋Pearl milk tea", "🥛 Milk", "🍹 Fruit tea", "🧉 Mate tea", "🍵 Matcha tea"]
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 10) {
ForEach(fruitItems, id: \.self) { fruit in
TeaItemView(title: fruit)
}
}
.padding(.horizontal, 20)
}
.background(.black)
}
}
draggedItem
是負責記錄要被拖曳的元件,再來Run 一下程式,會出現下面畫面

DrogDelegate
如果要讓List 的元件可以被拖曳,必須實作 DropDelegate 這個Protocol,新增一個 DropDragDelegate 來實作它
import SwiftUI
struct DropDrapDelegate: DropDelegate {
let destinationItem: String
@Binding var fruits: [String]
@Binding var draggedItem: String?
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
draggedItem = nil
return true
}
func dropEntered(info: DropInfo) {
if let draggedItem {
let fromIndex = fruits.firstIndex(of: draggedItem)
if let fromIndex {
let toIndex = fruits.firstIndex(of: destinationItem)
if let toIndex, fromIndex != toIndex {
withAnimation {
self.fruits.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex > fromIndex ? (toIndex + 1) : toIndex))
}
}
}
}
}
}
在這個 Delegate 中有幾個需要實作的 function
func dropUpdated(info: DropInfo) -> DropProposal?
宣告這個拖曳是要什麼動作,以範例來說只是要做移動,所以是 .move
,可以去官方文件查看 DropProposal
可以知道更多設定值,預設值會是 nil
。
func performDrop(info: DropInfo) -> Bool
拖曳完成的時候會呼叫到它,回傳 true
表示 Drop was successful
func dropEntered(info: DropInfo)
在這個拖曳要做些什麼? 這是我對這個function 的理解,這個範例主要是要交換順序,所以裡面寫的就是交換的方式
View
再回到 View ,將 OnDrop
與 OnDrag
加到剛剛寫好的 List 中
ScrollView(showsIndicators: false) {
VStack(spacing: 10) {
ForEach(fruitItems, id: \.self) { fruit in
TeaItemView(title: fruit)
.onDrag({
draggedItem = fruit
return NSItemProvider()
})
.onDrop(of: [.text], delegate: DropDrapDelegate(destinationItem: fruit, fruits: $fruitItems, draggedItem: $draggedItem))
}
}
.padding(.horizontal, 20)
}
.background(.black)
onDrag
這個的重點在於 NSItemProvider
,NSItemProvider
它是為了在進行 Drag-and-drop
或是 Copy-and-paste
時傳送資料或檔案用的,設定方式我自己是不太理解,常見的設定是 URL
,這個範例用不到,就直接建立一個空的ItemProvider,若有需要傳送資料到不同裝置上的話應該需要留意一下。
onDrop(of:delegate:)
設定Type與Delegate ,Delegate 就是先前建立的 DropDrapDelegate,直接代需要的參考就好。
Type的設定有二種方式,第一種是給它 The uniform type identifiers
,這是需要可以辨識要傳送的資料,查了一下,應該是可以自定這個 Identifier。第二種是設定 UTType
,在範例中的 [.text]
就是 UTType
裡的 Text
,在官方文件中可以看到很多種類型,那UTType
是什麼? 它其實就是指要進行load, send, or receive
的資料類型。
加上 onDrag
與 onDrop
之後就可以進行拖曳了

可以在 Github 上下載這個範例程式
最後祝大家 Coding 愉快!!