快速用户界面 |使用 onDrag 和 onDrop 在一个 LazyGrid 中重新排序项目?
SwiftUI | Using onDrag and onDrop to reorder Items within one single LazyGrid?
我想知道是否可以使用 View.onDrag
和 View.onDrop
在一个 LazyGrid
中手动添加拖放重新排序?
虽然我能够使用 onDrag
使每个项目都可拖动,但我不知道如何实现放置部分。
这是我正在试验的代码:
import SwiftUI
//MARK: - Data
struct Data: Identifiable {
let id: Int
}
//MARK: - Model
class Model: ObservableObject {
@Published var data: [Data]
let columns = [
GridItem(.fixed(160)),
GridItem(.fixed(160))
]
init() {
data = Array<Data>(repeating: Data(id: 0), count: 100)
for i in 0..<data.count {
data[i] = Data(id: i)
}
}
}
//MARK: - Grid
struct ContentView: View {
@StateObject private var model = Model()
var body: some View {
ScrollView {
LazyVGrid(columns: model.columns, spacing: 32) {
ForEach(model.data) { d in
ItemView(d: d)
.id(d.id)
.frame(width: 160, height: 240)
.background(Color.green)
.onDrag { return NSItemProvider(object: String(d.id) as NSString) }
}
}
}
}
}
//MARK: - GridItem
struct ItemView: View {
var d: Data
var body: some View {
VStack {
Text(String(d.id))
.font(.headline)
.foregroundColor(.white)
}
}
}
谢谢!
以下是实现放置部分的方法。但请记住,如果数据符合 UTType
,ondrop
可以允许从应用程序外部放入内容。更多关于 UTTypes.
将 onDrop 实例添加到您的 lazyVGrid。
LazyVGrid(columns: model.columns, spacing: 32) {
ForEach(model.data) { d in
ItemView(d: d)
.id(d.id)
.frame(width: 160, height: 240)
.background(Color.green)
.onDrag { return NSItemProvider(object: String(d.id) as NSString) }
}
}.onDrop(of: ["public.plain-text"], delegate: CardsDropDelegate(listData: $model.data))
创建一个 DropDelegate 以使用给定视图处理放置的内容和放置位置。
struct CardsDropDelegate: DropDelegate {
@Binding var listData: [MyData]
func performDrop(info: DropInfo) -> Bool {
// check if data conforms to UTType
guard info.hasItemsConforming(to: ["public.plain-text"]) else {
return false
}
let items = info.itemProviders(for: ["public.plain-text"])
for item in items {
_ = item.loadObject(ofClass: String.self) { data, _ in
// idea is to reindex data with dropped view
let index = Int(data!)
DispatchQueue.main.async {
// id of dropped view
print("View Id dropped \(index)")
}
}
}
return true
}
}
另外performDrop
唯一真正有用的参数是info.location
放置位置的CGPoint,将CGPoint映射到要替换的视图似乎不合理。我认为 OnMove
会是一个更好的选择,它会让移动 data/Views 变得轻而易举。我没有成功让 OnMove
在 LazyVGrid
中工作。
因为 LazyVGrid
仍处于测试阶段,并且肯定会发生变化。我会放弃使用更复杂的任务。
SwiftUI 2.0
这是可能方法的完整简单演示(没有对其进行太多调整,`因为代码增长速度与演示一样快)。
要点是:a) 重新排序不需要等待丢弃,因此应该即时跟踪; b) 为了避免与坐标共舞,处理网格项目视图的下降更简单; c) 在数据模型中找到移动到哪里并执行此操作,因此 SwiftUI 会自行为视图设置动画。
测试 Xcode 12b3 / iOS 14
import SwiftUI
import UniformTypeIdentifiers
struct GridData: Identifiable, Equatable {
let id: Int
}
//MARK: - Model
class Model: ObservableObject {
@Published var data: [GridData]
let columns = [
GridItem(.fixed(160)),
GridItem(.fixed(160))
]
init() {
data = Array(repeating: GridData(id: 0), count: 100)
for i in 0..<data.count {
data[i] = GridData(id: i)
}
}
}
//MARK: - Grid
struct DemoDragRelocateView: View {
@StateObject private var model = Model()
@State private var dragging: GridData?
var body: some View {
ScrollView {
LazyVGrid(columns: model.columns, spacing: 32) {
ForEach(model.data) { d in
GridItemView(d: d)
.overlay(dragging?.id == d.id ? Color.white.opacity(0.8) : Color.clear)
.onDrag {
self.dragging = d
return NSItemProvider(object: String(d.id) as NSString)
}
.onDrop(of: [UTType.text], delegate: DragRelocateDelegate(item: d, listData: $model.data, current: $dragging))
}
}.animation(.default, value: model.data)
}
}
}
struct DragRelocateDelegate: DropDelegate {
let item: GridData
@Binding var listData: [GridData]
@Binding var current: GridData?
func dropEntered(info: DropInfo) {
if item != current {
let from = listData.firstIndex(of: current!)!
let to = listData.firstIndex(of: item)!
if listData[to].id != current!.id {
listData.move(fromOffsets: IndexSet(integer: from),
toOffset: to > from ? to + 1 : to)
}
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
self.current = nil
return true
}
}
//MARK: - GridItem
struct GridItemView: View {
var d: GridData
var body: some View {
VStack {
Text(String(d.id))
.font(.headline)
.foregroundColor(.white)
}
.frame(width: 160, height: 240)
.background(Color.green)
}
}
编辑
以下是如何修复拖动项在拖放到任何网格项之外时永不消失的问题:
struct DropOutsideDelegate: DropDelegate {
@Binding var current: GridData?
func performDrop(info: DropInfo) -> Bool {
current = nil
return true
}
}
struct DemoDragRelocateView: View {
...
var body: some View {
ScrollView {
...
}
.onDrop(of: [UTType.text], delegate: DropOutsideDelegate(current: $dragging))
}
}
上面的优秀解决方案还提出了一些额外的问题,所以这是我在 1 月 1 日宿醉时可以想出的(即,为少于 eloquent 道歉):
- 如果您选择一个 griditem 并释放它(取消),则视图不会重置
我添加了一个布尔值来检查视图是否已被拖动,如果没有,那么它不会首先隐藏视图。这有点 hack,因为它并没有真正重置,它只是推迟隐藏视图,直到它知道你想拖动它。 IE。如果您拖动得非常快,您可以在隐藏之前短暂地看到视图。
- 如果将 griditem 拖放到视图外,则不会重置视图
这个问题已经通过添加 dropOutside 委托得到了部分解决,但是 SwiftUI 不会触发它,除非你有背景视图(比如颜色),我认为这引起了一些混乱。因此,我添加了一个灰色背景来说明如何正确触发它。
希望这对任何人有帮助:
import SwiftUI
import UniformTypeIdentifiers
struct GridData: Identifiable, Equatable {
let id: String
}
//MARK: - Model
class Model: ObservableObject {
@Published var data: [GridData]
let columns = [
GridItem(.flexible(minimum: 60, maximum: 60))
]
init() {
data = Array(repeating: GridData(id: "0"), count: 50)
for i in 0..<data.count {
data[i] = GridData(id: String("\(i)"))
}
}
}
//MARK: - Grid
struct DemoDragRelocateView: View {
@StateObject private var model = Model()
@State private var dragging: GridData? // I can't reset this when user drops view ins ame location as drag started
@State private var changedView: Bool = false
var body: some View {
VStack {
ScrollView(.vertical) {
LazyVGrid(columns: model.columns, spacing: 5) {
ForEach(model.data) { d in
GridItemView(d: d)
.opacity(dragging?.id == d.id && changedView ? 0 : 1)
.onDrag {
self.dragging = d
changedView = false
return NSItemProvider(object: String(d.id) as NSString)
}
.onDrop(of: [UTType.text], delegate: DragRelocateDelegate(item: d, listData: $model.data, current: $dragging, changedView: $changedView))
}
}.animation(.default, value: model.data)
}
}
.frame(maxWidth:.infinity, maxHeight: .infinity)
.background(Color.gray.edgesIgnoringSafeArea(.all))
.onDrop(of: [UTType.text], delegate: DropOutsideDelegate(current: $dragging, changedView: $changedView))
}
}
struct DragRelocateDelegate: DropDelegate {
let item: GridData
@Binding var listData: [GridData]
@Binding var current: GridData?
@Binding var changedView: Bool
func dropEntered(info: DropInfo) {
if current == nil { current = item }
changedView = true
if item != current {
let from = listData.firstIndex(of: current!)!
let to = listData.firstIndex(of: item)!
if listData[to].id != current!.id {
listData.move(fromOffsets: IndexSet(integer: from),
toOffset: to > from ? to + 1 : to)
}
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
changedView = false
self.current = nil
return true
}
}
struct DropOutsideDelegate: DropDelegate {
@Binding var current: GridData?
@Binding var changedView: Bool
func dropEntered(info: DropInfo) {
changedView = true
}
func performDrop(info: DropInfo) -> Bool {
changedView = false
current = nil
return true
}
}
//MARK: - GridItem
struct GridItemView: View {
var d: GridData
var body: some View {
VStack {
Text(String(d.id))
.font(.headline)
.foregroundColor(.white)
}
.frame(width: 60, height: 60)
.background(Circle().fill(Color.green))
}
}
这是我的解决方案(基于 Asperi 的回答),适用于那些为 ForEach
寻求通用方法的人,我 将视图抽象化了 :
struct ReorderableForEach<Content: View, Item: Identifiable & Equatable>: View {
let items: [Item]
let content: (Item) -> Content
let moveAction: (IndexSet, Int) -> Void
// A little hack that is needed in order to make view back opaque
// if the drag and drop hasn't ever changed the position
// Without this hack the item remains semi-transparent
@State private var hasChangedLocation: Bool = false
init(
items: [Item],
@ViewBuilder content: @escaping (Item) -> Content,
moveAction: @escaping (IndexSet, Int) -> Void
) {
self.items = items
self.content = content
self.moveAction = moveAction
}
@State private var draggingItem: Item?
var body: some View {
ForEach(items) { item in
content(item)
.overlay(draggingItem == item && hasChangedLocation ? Color.white.opacity(0.8) : Color.clear)
.onDrag {
draggingItem = item
return NSItemProvider(object: "\(item.id)" as NSString)
}
.onDrop(
of: [UTType.text],
delegate: DragRelocateDelegate(
item: item,
listData: items,
current: $draggingItem,
hasChangedLocation: $hasChangedLocation
) { from, to in
withAnimation {
moveAction(from, to)
}
}
)
}
}
}
DragRelocateDelegate
基本上保持不变,尽管我让它更通用、更安全:
struct DragRelocateDelegate<Item: Equatable>: DropDelegate {
let item: Item
var listData: [Item]
@Binding var current: Item?
@Binding var hasChangedLocation: Bool
var moveAction: (IndexSet, Int) -> Void
func dropEntered(info: DropInfo) {
guard item != current, let current = current else { return }
guard let from = listData.firstIndex(of: current), let to = listData.firstIndex(of: item) else { return }
hasChangedLocation = true
if listData[to] != current {
moveAction(IndexSet(integer: from), to > from ? to + 1 : to)
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
hasChangedLocation = false
current = nil
return true
}
}
最后是实际用法:
ReorderableForEach(items: itemsArr) { item in
SomeFancyView(for: item)
} moveAction: { from, to in
itemsArr.move(fromOffsets: from, toOffset: to)
}
目标:重新排序 HStack 中的项目
我试图弄清楚在将图标拖动到 re-order 一组水平项目时如何在 SwiftUI for macOS 中利用此解决方案。感谢@ramzesenok 和@Asperi 的整体解决方案。我添加了一个 CGPoint 属性 以及他们的解决方案以实现所需的行为。请看下面的动画。
定义点
@State private var drugItemLocation: CGPoint?
我在 dropEntered
、dropExited
和 performDrop
DropDelegate 函数中使用。
func dropEntered(info: DropInfo) {
if current == nil {
current = item
drugItemLocation = info.location
}
guard item != current,
let current = current,
let from = icons.firstIndex(of: current),
let toIndex = icons.firstIndex(of: item) else { return }
hasChangedLocation = true
drugItemLocation = info.location
if icons[toIndex] != current {
icons.move(fromOffsets: IndexSet(integer: from), toOffset: toIndex > from ? toIndex + 1 : toIndex)
}
}
func dropExited(info: DropInfo) {
drugItemLocation = nil
}
func performDrop(info: DropInfo) -> Bool {
hasChangedLocation = false
drugItemLocation = nil
current = nil
return true
}
为了完整演示,我使用 Playgrounds
创建了一个 gist
我想知道是否可以使用 View.onDrag
和 View.onDrop
在一个 LazyGrid
中手动添加拖放重新排序?
虽然我能够使用 onDrag
使每个项目都可拖动,但我不知道如何实现放置部分。
这是我正在试验的代码:
import SwiftUI
//MARK: - Data
struct Data: Identifiable {
let id: Int
}
//MARK: - Model
class Model: ObservableObject {
@Published var data: [Data]
let columns = [
GridItem(.fixed(160)),
GridItem(.fixed(160))
]
init() {
data = Array<Data>(repeating: Data(id: 0), count: 100)
for i in 0..<data.count {
data[i] = Data(id: i)
}
}
}
//MARK: - Grid
struct ContentView: View {
@StateObject private var model = Model()
var body: some View {
ScrollView {
LazyVGrid(columns: model.columns, spacing: 32) {
ForEach(model.data) { d in
ItemView(d: d)
.id(d.id)
.frame(width: 160, height: 240)
.background(Color.green)
.onDrag { return NSItemProvider(object: String(d.id) as NSString) }
}
}
}
}
}
//MARK: - GridItem
struct ItemView: View {
var d: Data
var body: some View {
VStack {
Text(String(d.id))
.font(.headline)
.foregroundColor(.white)
}
}
}
谢谢!
以下是实现放置部分的方法。但请记住,如果数据符合 UTType
,ondrop
可以允许从应用程序外部放入内容。更多关于 UTTypes.
将 onDrop 实例添加到您的 lazyVGrid。
LazyVGrid(columns: model.columns, spacing: 32) {
ForEach(model.data) { d in
ItemView(d: d)
.id(d.id)
.frame(width: 160, height: 240)
.background(Color.green)
.onDrag { return NSItemProvider(object: String(d.id) as NSString) }
}
}.onDrop(of: ["public.plain-text"], delegate: CardsDropDelegate(listData: $model.data))
创建一个 DropDelegate 以使用给定视图处理放置的内容和放置位置。
struct CardsDropDelegate: DropDelegate {
@Binding var listData: [MyData]
func performDrop(info: DropInfo) -> Bool {
// check if data conforms to UTType
guard info.hasItemsConforming(to: ["public.plain-text"]) else {
return false
}
let items = info.itemProviders(for: ["public.plain-text"])
for item in items {
_ = item.loadObject(ofClass: String.self) { data, _ in
// idea is to reindex data with dropped view
let index = Int(data!)
DispatchQueue.main.async {
// id of dropped view
print("View Id dropped \(index)")
}
}
}
return true
}
}
另外performDrop
唯一真正有用的参数是info.location
放置位置的CGPoint,将CGPoint映射到要替换的视图似乎不合理。我认为 OnMove
会是一个更好的选择,它会让移动 data/Views 变得轻而易举。我没有成功让 OnMove
在 LazyVGrid
中工作。
因为 LazyVGrid
仍处于测试阶段,并且肯定会发生变化。我会放弃使用更复杂的任务。
SwiftUI 2.0
这是可能方法的完整简单演示(没有对其进行太多调整,`因为代码增长速度与演示一样快)。
要点是:a) 重新排序不需要等待丢弃,因此应该即时跟踪; b) 为了避免与坐标共舞,处理网格项目视图的下降更简单; c) 在数据模型中找到移动到哪里并执行此操作,因此 SwiftUI 会自行为视图设置动画。
测试 Xcode 12b3 / iOS 14
import SwiftUI
import UniformTypeIdentifiers
struct GridData: Identifiable, Equatable {
let id: Int
}
//MARK: - Model
class Model: ObservableObject {
@Published var data: [GridData]
let columns = [
GridItem(.fixed(160)),
GridItem(.fixed(160))
]
init() {
data = Array(repeating: GridData(id: 0), count: 100)
for i in 0..<data.count {
data[i] = GridData(id: i)
}
}
}
//MARK: - Grid
struct DemoDragRelocateView: View {
@StateObject private var model = Model()
@State private var dragging: GridData?
var body: some View {
ScrollView {
LazyVGrid(columns: model.columns, spacing: 32) {
ForEach(model.data) { d in
GridItemView(d: d)
.overlay(dragging?.id == d.id ? Color.white.opacity(0.8) : Color.clear)
.onDrag {
self.dragging = d
return NSItemProvider(object: String(d.id) as NSString)
}
.onDrop(of: [UTType.text], delegate: DragRelocateDelegate(item: d, listData: $model.data, current: $dragging))
}
}.animation(.default, value: model.data)
}
}
}
struct DragRelocateDelegate: DropDelegate {
let item: GridData
@Binding var listData: [GridData]
@Binding var current: GridData?
func dropEntered(info: DropInfo) {
if item != current {
let from = listData.firstIndex(of: current!)!
let to = listData.firstIndex(of: item)!
if listData[to].id != current!.id {
listData.move(fromOffsets: IndexSet(integer: from),
toOffset: to > from ? to + 1 : to)
}
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
self.current = nil
return true
}
}
//MARK: - GridItem
struct GridItemView: View {
var d: GridData
var body: some View {
VStack {
Text(String(d.id))
.font(.headline)
.foregroundColor(.white)
}
.frame(width: 160, height: 240)
.background(Color.green)
}
}
编辑
以下是如何修复拖动项在拖放到任何网格项之外时永不消失的问题:
struct DropOutsideDelegate: DropDelegate {
@Binding var current: GridData?
func performDrop(info: DropInfo) -> Bool {
current = nil
return true
}
}
struct DemoDragRelocateView: View {
...
var body: some View {
ScrollView {
...
}
.onDrop(of: [UTType.text], delegate: DropOutsideDelegate(current: $dragging))
}
}
上面的优秀解决方案还提出了一些额外的问题,所以这是我在 1 月 1 日宿醉时可以想出的(即,为少于 eloquent 道歉):
- 如果您选择一个 griditem 并释放它(取消),则视图不会重置
我添加了一个布尔值来检查视图是否已被拖动,如果没有,那么它不会首先隐藏视图。这有点 hack,因为它并没有真正重置,它只是推迟隐藏视图,直到它知道你想拖动它。 IE。如果您拖动得非常快,您可以在隐藏之前短暂地看到视图。
- 如果将 griditem 拖放到视图外,则不会重置视图
这个问题已经通过添加 dropOutside 委托得到了部分解决,但是 SwiftUI 不会触发它,除非你有背景视图(比如颜色),我认为这引起了一些混乱。因此,我添加了一个灰色背景来说明如何正确触发它。
希望这对任何人有帮助:
import SwiftUI
import UniformTypeIdentifiers
struct GridData: Identifiable, Equatable {
let id: String
}
//MARK: - Model
class Model: ObservableObject {
@Published var data: [GridData]
let columns = [
GridItem(.flexible(minimum: 60, maximum: 60))
]
init() {
data = Array(repeating: GridData(id: "0"), count: 50)
for i in 0..<data.count {
data[i] = GridData(id: String("\(i)"))
}
}
}
//MARK: - Grid
struct DemoDragRelocateView: View {
@StateObject private var model = Model()
@State private var dragging: GridData? // I can't reset this when user drops view ins ame location as drag started
@State private var changedView: Bool = false
var body: some View {
VStack {
ScrollView(.vertical) {
LazyVGrid(columns: model.columns, spacing: 5) {
ForEach(model.data) { d in
GridItemView(d: d)
.opacity(dragging?.id == d.id && changedView ? 0 : 1)
.onDrag {
self.dragging = d
changedView = false
return NSItemProvider(object: String(d.id) as NSString)
}
.onDrop(of: [UTType.text], delegate: DragRelocateDelegate(item: d, listData: $model.data, current: $dragging, changedView: $changedView))
}
}.animation(.default, value: model.data)
}
}
.frame(maxWidth:.infinity, maxHeight: .infinity)
.background(Color.gray.edgesIgnoringSafeArea(.all))
.onDrop(of: [UTType.text], delegate: DropOutsideDelegate(current: $dragging, changedView: $changedView))
}
}
struct DragRelocateDelegate: DropDelegate {
let item: GridData
@Binding var listData: [GridData]
@Binding var current: GridData?
@Binding var changedView: Bool
func dropEntered(info: DropInfo) {
if current == nil { current = item }
changedView = true
if item != current {
let from = listData.firstIndex(of: current!)!
let to = listData.firstIndex(of: item)!
if listData[to].id != current!.id {
listData.move(fromOffsets: IndexSet(integer: from),
toOffset: to > from ? to + 1 : to)
}
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
changedView = false
self.current = nil
return true
}
}
struct DropOutsideDelegate: DropDelegate {
@Binding var current: GridData?
@Binding var changedView: Bool
func dropEntered(info: DropInfo) {
changedView = true
}
func performDrop(info: DropInfo) -> Bool {
changedView = false
current = nil
return true
}
}
//MARK: - GridItem
struct GridItemView: View {
var d: GridData
var body: some View {
VStack {
Text(String(d.id))
.font(.headline)
.foregroundColor(.white)
}
.frame(width: 60, height: 60)
.background(Circle().fill(Color.green))
}
}
这是我的解决方案(基于 Asperi 的回答),适用于那些为 ForEach
寻求通用方法的人,我 将视图抽象化了 :
struct ReorderableForEach<Content: View, Item: Identifiable & Equatable>: View {
let items: [Item]
let content: (Item) -> Content
let moveAction: (IndexSet, Int) -> Void
// A little hack that is needed in order to make view back opaque
// if the drag and drop hasn't ever changed the position
// Without this hack the item remains semi-transparent
@State private var hasChangedLocation: Bool = false
init(
items: [Item],
@ViewBuilder content: @escaping (Item) -> Content,
moveAction: @escaping (IndexSet, Int) -> Void
) {
self.items = items
self.content = content
self.moveAction = moveAction
}
@State private var draggingItem: Item?
var body: some View {
ForEach(items) { item in
content(item)
.overlay(draggingItem == item && hasChangedLocation ? Color.white.opacity(0.8) : Color.clear)
.onDrag {
draggingItem = item
return NSItemProvider(object: "\(item.id)" as NSString)
}
.onDrop(
of: [UTType.text],
delegate: DragRelocateDelegate(
item: item,
listData: items,
current: $draggingItem,
hasChangedLocation: $hasChangedLocation
) { from, to in
withAnimation {
moveAction(from, to)
}
}
)
}
}
}
DragRelocateDelegate
基本上保持不变,尽管我让它更通用、更安全:
struct DragRelocateDelegate<Item: Equatable>: DropDelegate {
let item: Item
var listData: [Item]
@Binding var current: Item?
@Binding var hasChangedLocation: Bool
var moveAction: (IndexSet, Int) -> Void
func dropEntered(info: DropInfo) {
guard item != current, let current = current else { return }
guard let from = listData.firstIndex(of: current), let to = listData.firstIndex(of: item) else { return }
hasChangedLocation = true
if listData[to] != current {
moveAction(IndexSet(integer: from), to > from ? to + 1 : to)
}
}
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
hasChangedLocation = false
current = nil
return true
}
}
最后是实际用法:
ReorderableForEach(items: itemsArr) { item in
SomeFancyView(for: item)
} moveAction: { from, to in
itemsArr.move(fromOffsets: from, toOffset: to)
}
目标:重新排序 HStack 中的项目
我试图弄清楚在将图标拖动到 re-order 一组水平项目时如何在 SwiftUI for macOS 中利用此解决方案。感谢@ramzesenok 和@Asperi 的整体解决方案。我添加了一个 CGPoint 属性 以及他们的解决方案以实现所需的行为。请看下面的动画。
定义点
@State private var drugItemLocation: CGPoint?
我在 dropEntered
、dropExited
和 performDrop
DropDelegate 函数中使用。
func dropEntered(info: DropInfo) {
if current == nil {
current = item
drugItemLocation = info.location
}
guard item != current,
let current = current,
let from = icons.firstIndex(of: current),
let toIndex = icons.firstIndex(of: item) else { return }
hasChangedLocation = true
drugItemLocation = info.location
if icons[toIndex] != current {
icons.move(fromOffsets: IndexSet(integer: from), toOffset: toIndex > from ? toIndex + 1 : toIndex)
}
}
func dropExited(info: DropInfo) {
drugItemLocation = nil
}
func performDrop(info: DropInfo) -> Bool {
hasChangedLocation = false
drugItemLocation = nil
current = nil
return true
}
为了完整演示,我使用 Playgrounds
创建了一个 gist