首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >SwiftUI使用onDrag和onDrop在一个LazyGrid中重新排序项目?

SwiftUI使用onDrag和onDrop在一个LazyGrid中重新排序项目?
EN

Stack Overflow用户
提问于 2020-06-27 06:53:34
回答 6查看 18.9K关注 0票数 46

我想知道是否可以使用View.onDragView.onDrop手动在一个LazyGrid中添加拖放重新排序?

虽然我能够使用onDrag使每个项目都是可拖动的,但我不知道如何实现删除部分。

下面是我正在试验的代码:

代码语言:javascript
运行
复制
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)
        }
    }
}

谢谢!

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2020-08-16 15:12:22

SwiftUI 2.0

这里完成了可能的方法的简单演示(没有对其进行太多的调优,`因为代码增长得很快,而不是演示)。

重要的一点是:( a)重新排序不假设等待删除,因此应该动态跟踪;( b)为了避免使用坐标跳舞--通过网格项视图处理drop更简单;( c)在数据模型中查找要移动的内容并执行此操作,因此SwiftUI可以自己动画视图。

用Xcode 12b3 / iOS 14测试

代码语言:javascript
运行
复制
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)
    }
}

编辑

下面是如何修复在任何网格项之外放置的永不消失的拖动项:

代码语言:javascript
运行
复制
struct DropOutsideDelegate: DropDelegate { 
    @Binding var current: GridData?  
        
    func performDrop(info: DropInfo) -> Bool {
        current = nil
        return true
    }
}
代码语言:javascript
运行
复制
struct DemoDragRelocateView: View {
    ...

    var body: some View {
        ScrollView {
            ...
        }
        .onDrop(of: [UTType.text], delegate: DropOutsideDelegate(current: $dragging))
    }
}
票数 84
EN

Stack Overflow用户

发布于 2021-08-28 11:38:16

下面是我的解决方案(基于Asperi的答案),对于那些寻求ForEach通用方法的人,将视图抽象为

代码语言:javascript
运行
复制
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基本上保持不变,尽管我使它更通用、更安全:

代码语言:javascript
运行
复制
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
    }
}

最后,这里是实际用法:

代码语言:javascript
运行
复制
ReorderableForEach(items: itemsArr) { item in
    SomeFancyView(for: item)
} moveAction: { from, to in
    itemsArr.move(fromOffsets: from, toOffset: to)
}
票数 21
EN

Stack Overflow用户

发布于 2021-01-01 15:15:02

上面的优秀解决方案还提出了一些额外的问题,因此,我可以在1月1日提出一些宿醉的问题(例如,为自己不够雄辩而道歉):

  1. 如果您选择一个网格项并释放它(要取消),则该视图不会被重置。

我添加了一个bool,它检查视图是否已经被拖动,如果没有,那么它一开始就没有隐藏视图。这有点麻烦,因为它并没有真正重置,它只是推迟隐藏视图,直到它知道您想要拖动它。也就是说,如果你拖得很快,你可以在视图隐藏之前看到它。

  1. 如果将网格项放置在视图之外,则不会重置视图。

通过添加dropOutside委托,已经部分解决了这个问题,但是SwiftUI不会触发它,除非您有一个背景视图(比如颜色),我认为这引起了一些混乱。因此,我添加了一个灰色背景,以说明如何正确触发它。

希望这对任何人都有帮助

代码语言:javascript
运行
复制
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))
    }
}
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62606907

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档