Drag item from one List to another List in SwiftUI
Asked Answered
A

1

7

I have two Lists each with simple items. I can rearrange items within a list, with the code shown below. I want to be able to drag an item from one list and drop it into the other list. Not sure what I need to enable to make that happen. As is, I can drag something from one list all over the screen, but I can't drop it anywhere except within its own list; if I release the drag anywhere else, it just flies back to its original location.

Clearly, I need to add something(s) to enable the desired behavior; any ideas on what's required (not necessarily using .onMove -- that's within the same list) would be most appreciated.

struct ContentView: View {
    @State private var users1 = ["AAA", "BBB", "CCC"]
    @State private var users2 = ["DDD", "EEE", "FFF"]
    @State private var isEditable = true

    var body: some View {
        HStack {
            Spacer()
            List {
                ForEach(users1, id: \.self) { user in
                    Text(user)
                        .background(Color(.yellow))
                }
                .onMove(perform: move1)
            }
            .environment(\.editMode, isEditable ? .constant(.active) : .constant(.inactive))
            Spacer()
            List {
                ForEach(users2, id: \.self) { user in
                    Text(user)
                        .background(Color(.orange))
                }
                .onMove(perform: move2)
            }
            .environment(\.editMode, isEditable ? .constant(.active) : .constant(.inactive))
            Spacer()
        }
    }
    func move1(from source: IndexSet, to destination: Int) {
        users1.move(fromOffsets: source, toOffset: destination)
    }
    func move2(from source: IndexSet, to destination: Int) {
        users2.move(fromOffsets: source, toOffset: destination)
    }
}
Albuminuria answered 21/10, 2019 at 19:8 Comment(3)
As is, you cannot simply cannot do this using the onMove modifier. Maybe someday, but not with SwiftUI "1.0". Maybe you can (1) add your own EditMode, (2) tap into gestures and positions, (3) add in your own animation and (4) basically define the entire thing - but at that point one has to ask a few things: (1) Why not do it in UIKit instead and make it representable? (2) Could you use a navigation bar instead, navigating between lists? Finally...Wives
I'm not clear how a Navigation Bar would enable dragging as described. And yes, UIKit is the fallback option, although I'd like reassurance that this really isn't possible in SwiftUI before going that route. And no, re-writing List() with this added feature isn't really what I have in mind.Albuminuria
Never meant to imply that a nav bar could handle dragging. I only meant it could be a (drastic) change to things. That was my second comment that I see never posted - pushing back on this requirement. Basically - and one of my favorite things to ask the requestor - can you show me an app that does what you want me to develop? If not, then maybe you need to redesign your requirements. Anyways, good luck.Wives
M
2

I don't think this is possible with SwiftUI yet. This piece of code shows how you use two UITableViews. I hope you can improve this to achieve what you are looking for.

First create this class which does all the magic. You need to import UIKit and MobileCoreServices.

class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITableViewDragDelegate, UITableViewDropDelegate {

    var leftTableView = UITableView()
    var rightTableView = UITableView()

    var removeIndex = IndexPath()

    var leftItems: [String] = [
        "Hello",
        "What",
        "is",
        "happening"
    ]
    var rightItems: [String] = [
        "I",
        "don't",
        "know"
    ]

    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        let string = tableView == leftTableView ? leftItems[indexPath.row] : rightItems[indexPath.row]
        self.removeIndex = indexPath
        guard let data = string.data(using: .utf8) else { return [] }
        let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypePlainText as String)

        return [UIDragItem(itemProvider: itemProvider)]
    }

    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
        let destinationIndexPath: IndexPath

        if let indexPath = coordinator.destinationIndexPath {
            destinationIndexPath = indexPath
        } else {
            let section = tableView.numberOfSections - 1
            let row = tableView.numberOfRows(inSection: section)
            destinationIndexPath = IndexPath(row: row, section: section)
        }

        coordinator.session.loadObjects(ofClass: NSString.self) { items in
            guard let strings = items as? [String] else {
                return
            }
            var indexPaths = [IndexPath]()
            for (index, string) in strings.enumerated() {
                let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
                if tableView == self.leftTableView {
                    self.leftItems.insert(string, at: indexPath.row)
                } else {
                    self.rightItems.insert(string, at: indexPath.row)
                }
                indexPaths.append(indexPath)
            }
            if tableView == self.leftTableView {
                self.rightItems.remove(at: self.removeIndex.row)
                self.rightTableView.deleteRows(at: [self.removeIndex], with: .automatic)
            } else {
                self.leftItems.remove(at: self.removeIndex.row)
                self.leftTableView.deleteRows(at: [self.removeIndex], with: .automatic)
            }
            tableView.insertRows(at: indexPaths, with: .automatic)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        leftTableView.dataSource = self
        rightTableView.dataSource = self

        leftTableView.frame = CGRect(x: 0, y: 40, width: 150, height: 400)
        rightTableView.frame = CGRect(x: 150, y: 40, width: 150, height: 400)

        leftTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        rightTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

        view.addSubview(leftTableView)
        view.addSubview(rightTableView)

        leftTableView.dragDelegate = self
        leftTableView.dropDelegate = self
        rightTableView.dragDelegate = self
        rightTableView.dropDelegate = self

        leftTableView.dragInteractionEnabled = true
        rightTableView.dragInteractionEnabled = true
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == leftTableView {
            return leftItems.count
        } else {
            return rightItems.count
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        if tableView == leftTableView {
            cell.textLabel?.text = leftItems[indexPath.row]
        } else {
            cell.textLabel?.text = rightItems[indexPath.row]
        }

        return cell
    }
}

After that you wrap this:

struct TableView: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> TableViewController {
        let v = TableViewController()
        return v
    }

    func updateUIViewController(_ viewController: TableViewController, context: Context) {

    }

}

and finally use it:

struct ContentView: View {


    var body: some View {
        VStack {
            TableView()
        }
    }
}

I hope this helps.

Motorcade answered 30/10, 2019 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.