Dragging list rows between sections

I am making an iPhone-first app which has a 2 level data structure. I want the user to be able to drag List rows from section to section, but not reorder the sections. Is there a SwiftUI way to do it, or should I resort to UIKit?

The basic problem seems to be that ForEach.onMove only allows reordering within its nearest datasource. That seems sensible as a general facility.

As it stands now drags can only happen within a section.
Code Block Swift
ForEach(groups) { group in
Section(header: Text(group.text)) {
ForEach(group.items) { item in
Text(item.text)
}
}
}
.onMove { … }

I might be willing to dive into UIKit to get this done, if it'll handle it. Right now I've flattened the data and marked some items as "containers" and others as "leaves". All get emitted as a dumb flat list:
Code Block Swift
ForEach(items) { item in
if item.isContainer {
Text(item.text).bold()
}
else {
Text(item.text)
}
}
.onMove { … }

The major downside is users can move the container "section header" lines.

Drag and drop doesn't seem to work on iPhone, just iPads. I'm kinda shy about hierarchical lists using List(_,children:) because I expect move would still allow top level items (my containers) would be allowed to be moved.
Aw heck, I'll just do this bit in UIKit. It supports rearranging rows across sections.
I'm also having this issue. Would love to know how this can be done in SwiftUI

Drag and drop doesn't seem to work on iPhone, just iPads. I'm kinda shy about hierarchical lists using List(_,children:)because I expect move would still allow top level items (my containers) would be allowed to be moved.

I'd argue it's even worse than that: as far as I can tell (having played around with Lists now to achieve what you are looking to achieve) there is no .onMove when the list is built with List(_ data:selection:rowContent:) – the .onMove modifier is as far as I can tell really only available when building out the list with ForEach.

ForEach has no clue of hierarchical items though the way I see it...

Trying to come up with a way to enable the user to move an item to another section I ended up showing buttons for all the sections in contextMenu within ForEach. When clicking one of the buttons, I remove the selected item from one section and append it to the end of the selected section.

Cumbersome, but works... I hope that there will be a more swift solution coming though...



You can achieve this behavior if you're not actually using a section header, but have a custom item act as one.

By setting .moveDisabled() to true the item can't be moved. You can then style the header the way you want.

Here's a small example i've tried out.

class SelectionViewModel: ObservableObject {
    struct Item {
        let title: String
        let isHeader: Bool
    }
    @Published var sections: [Item] = [
        .init(title: "Selected", isHeader: true),
        .init(title: "A", isHeader: false),
        .init(title: "B", isHeader: false),
        .init(title: "C", isHeader: false),
        .init(title: "Unselected", isHeader: true),
        .init(title: "D", isHeader: false),
        .init(title: "E", isHeader: false),
        .init(title: "F", isHeader: false),

    ]
}

struct SelectionView: View {
    @ObservedObject var selectionViewModel: SelectionViewModel

    var body: some View {
        List {
            ForEach(selectionViewModel.sections, id: \.title) { item in
                if item.isHeader {
                    Text("Section header \(item.title)")
                        .moveDisabled(true)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding([.top, .bottom])
                        .background(Color.gray)

                } else {
                    Text(item.title)
                        .moveDisabled(item.isHeader)
                }
            }.onMove(perform: { indices, newOffset in
                print("should move")
            })
        }.environment(\.editMode, .constant(.active))
    }
}

struct SelectionView_Previews: PreviewProvider {
    static var previews: some View {
        SelectionView(selectionViewModel: .init())
    }
}

Dragging list rows between sections
 
 
Q