简体   繁体   中英

SwiftUI: Running into issues with Drag and Drop inside a LazyVGrid

I've been working on my own smart home app and have run into some issues when trying to build the grid for the app.

I've been basing this home app on this tutorial. The goal is that one can reorder the individually sized blocks in the grid basically like he or she wants. The blocks(items) represent different gadgets in the smart home application. The issue I'm facing is that I can't seem to get the drag & drop to work. Maybe it's better to put all the item views in one custom view and then run a "ForEach" loop for them so that the.onDrag works? I'm relatively new to SwiftUI so I appreciate every hint I can get this program to work.

Here is my code:

ItemModel1:

struct ItemModel: Identifiable, Equatable {
let id: String
var title: String

init(id: String = UUID().uuidString, title: String) {
    self.id = id
    self.title = title

    }

    func updateCompletion() -> ItemModel {
        return ItemModel(id: id, title: title)
    }
    }

ItemModel2:

    struct ItemModel2: Identifiable, Equatable {
        let id: String
        var title: String
        
        init(id: String = UUID().uuidString, title: String) {
            self.id = id
            self.title = title

    }

    func updateCompletion() -> ItemModel2 {
        return ItemModel2(id: id, title: title)
    }
    }

It's essentially the same for the two other ItemModels 3 and 4..

ItemViewModel:

    class ItemViewModel {
        var items: [ItemModel] = []
        @Published var currentGrid: ItemModel?
        
        init() {
            getItems()
        }
        
        func getItems() {
            let newItems = [
                ItemModel(title: "Item1"),
            ]
            items.append(contentsOf: newItems)
        }
        
        func addItem(title: String) {
            let newItem = ItemModel(title: title)
            items.append(newItem)
        }
        
        func updateItem(item: ItemModel) {
            
            if let index = items.firstIndex(where: { $0.id == item.id}) {
                items[index] = item.updateCompletion()
            }
        }
    }

ContentView:

        struct DropViewDelegate: DropDelegate {
           
            var grid: ItemModel
            var gridData: ItemViewModel
            

            
            func performDrop(info: DropInfo) -> Bool {

                return true
            }
            
            func dropEntered(info: DropInfo) {

                let fromIndex = gridData.items.firstIndex { (grid) -> Bool in
                    return self.grid.id == gridData.currentGrid?.id
                } ?? 0
                
                let toIndex = gridData.items.firstIndex { (grid) -> Bool in
                    return self.grid.id == self.grid.id
                } ?? 0
                
                if fromIndex != toIndex{
                    withAnimation(.default){
                        let fromGrid = gridData.items[fromIndex]
                        gridData.items[fromIndex] = gridData.items[toIndex]
                        gridData.items[toIndex] = fromGrid
                    }
                }
            }
            

            func dropUpdated(info: DropInfo) -> DropProposal? {
                return DropProposal(operation: .move)
            }
        }



        struct ContentView: View {
            @State var items: [ItemModel] = []
            @State var items2: [ItemModel2] = []
            @State var items3: [ItemModel3] = []
            @State var items4: [ItemModel4] = []
            @State var gridData = ItemViewModel()
            
            let columns = [
                GridItem(.adaptive(minimum: 160)),
                GridItem(.adaptive(minimum: 160)),
            ]
            let columns2 = [
                GridItem(.flexible()),
            ]
            
            
            var body: some View {
                ZStack{
                    ScrollView{
                        VStack{
                            HStack(alignment: .top){
                                
                                Button(action: saveButtonPressed, label: {
                                    Text("Item1")
                                        .font(.title2)
                                                                
                           .foregroundColor(.white)
                                })
                                Button(action: saveButtonPressed2, label: {
                                    Text("Item2")
                                        .font(.title2)
                                                                
                           .foregroundColor(.white)
                                })
                                Button(action: saveButtonPressed3, label: {
                                    Text("Item3")
                                        .font(.title2)
                                                                
                             .foregroundColor(.white)
                                })
                                Button(action: saveButtonPressed4, label: {
                                    Text("Item4")
                                        .font(.title2)
                                                               
                         .foregroundColor(.white)
                                })
                                
                            }
                            
                            
                            
                            LazyVGrid(
                                columns: columns,
                                alignment: .leading,
                                spacing: 12
                            ){
                                ForEach(items) { item in
                                    Item1View (item: item)
                                    if 1 == 1 { Color.clear }
                                }
                                
                                ForEach(items4) { item4 in
                                    Item4View (item4: item4)
                                    if 1 == 1 { Color.clear }
                                }
                                
                                ForEach(items2) { item2 in
                                    Item2View (item2: item2)
                                }
                                LazyVGrid(
                                    columns: columns2,
                                    alignment: .leading,
                                    spacing: 12
                                ){
                                    ForEach(items3) { item3 in
                                        Item3View (item3: item3)
                                    }
                                }
                            }
                            .onDrag({
                            self.gridData = items
                            return NSItemProvider(item: nil, typeIdentifier: 
                          self.grid)
                            })
                            .onDrop(of: [items], delegate: DropViewDelegate(grid: 
                         items, gridData: gridData))
                     
                     
                        }
                    }
                }
            }
            
            func saveButtonPressed() {
                addItem(title: "Hello")
            }
            
            func addItem(title: String) {
                let newItem = ItemModel(title: title)
                items.append(newItem)
            }
            func saveButtonPressed2() {
                addItem2(title: "Hello")
            }
            
            func addItem2(title: String) {
                let newItem = ItemModel2(title: title)
                items2.append(newItem)
            }
            func saveButtonPressed3() {
                addItem3(title: "Hello")
            }
            
            func addItem3(title: String) {
                let newItem = ItemModel3(title: title)
                items3.append(newItem)
            }
            func saveButtonPressed4() {
                addItem4(title: "Hello")
            }
            
            func addItem4(title: String) {
                let newItem = ItemModel4(title: title)
                items4.append(newItem)
            }
            
            
            
            struct ContentView_Previews: PreviewProvider {
                static var previews: some View {
                    ContentView()
                }
            }
            
        }

Item1:

            struct Item1View: View {
               @State var item: ItemModel

            var body: some View {
                
                    HStack {
                        Text(item.title)
                    }
                    .padding( )
                    .frame(width: 396, height: 56)
                    .background(.black)
                    .cornerRadius(12.0)

            }
            }

Item2:

            struct Item2View: View {
               @State var item2: ItemModel2

            var body: some View {
                
                    HStack {
                        Text(item2.title)
                    }
                    .padding( )
                    .frame(width: 182, height: 132)
                    .background(.black)
                    .cornerRadius(12.0)

            }
            }

Item3:

            struct Item3View: View {
               @State var item3: ItemModel3

            var body: some View {
                
                    HStack {
                        Text(item3.title)
                    }
                    .padding( )
                    .frame(width: 182, height: 62)
                    .background(.black)
                    .cornerRadius(12.0)

            }
            }

Item4:

            struct Item4View: View {
               @State var item4: ItemModel4

            var body: some View {
                
                    HStack {
                        Text(item4.title)
                    }
                    .padding( )
                    .frame(width: 396, height: 156)
                    .background(.black)
                    .cornerRadius(12.0)

            }
            }

I tried recreating the grid Asperi linked. However, the.onDrop doesn't seem to work like it should. The drop only occurs after you pressed another item to drag it. Only then will the previous items reorder themselves..

My version:

import SwiftUI
import UniformTypeIdentifiers

struct ItemModel6: Identifiable, Equatable {
let id: String
var title: String

init(id: String = UUID().uuidString, title: String) {
    self.id = id
    self.title = title

}

func updateCompletion() -> ItemModel6 {
return ItemModel6(id: id, title: title)
}
}



class Model: ObservableObject {
var data: [ItemModel6] = []

let columns = [
    GridItem(.adaptive(minimum: 160)),
    GridItem(.adaptive(minimum: 160)),
]

init() {
    data = Array(repeating: ItemModel6(title: "title"), count: 
    100)
    for i in 0..<data.count {
        data[i] = ItemModel6(title: "Hello")
    }
    }
    }



struct DemoDragRelocateView: View {
@StateObject private var model = Model()

@State private var dragging: ItemModel6?

var body: some View {
    ScrollView {
       LazyVGrid(columns: model.columns) {
           ForEach(model.data) { item2 in GridItemView (item2: 
     item2)
                    .overlay(dragging?.id == item2.id ? 
        Color.white.opacity(0.8) : Color.clear)
                    .onDrag {
                        self.dragging = item2
                        return NSItemProvider(object: 
        String(item2.id) as NSString)
                    }
                    .onDrop(of: [UTType.text], delegate: 
        DragRelocateDelegate(item: item2, listData: $model.data, 
          current: $dragging))

            }
        }.animation(.default, value: model.data)
    }
    .onDrop(of: [UTType.text], delegate: 
   DropOutsideDelegate(current: $dragging))
    }
}
struct DropOutsideDelegate: DropDelegate {
@Binding var current: ItemModel6?
    
func performDrop(info: DropInfo) -> Bool {
    current = nil
    return true
}
    }

struct DragRelocateDelegate: DropDelegate {
let item: ItemModel6
@Binding var listData: [ItemModel6]
@Binding var current: ItemModel6?

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
}
}



struct GridItemView: View {
@State var item2: ItemModel6

    var body: some View {
 
     HStack {
         Text(item2.title)
     }
     .padding( )
     .frame(width: 182, height: 132)
     .background(.gray)
     .cornerRadius(12.0)

    }
}


struct DemoDragRelocateView_Previews: PreviewProvider {
static var previews: some View {
    DemoDragRelocateView()
        .preferredColorScheme(.dark)
}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM