如何在SwiftUI中使用onMove拖动(和长按)列表项时移除背景?

7
我在SwiftUI中有一个列表,如下所示:
@State var items = ["item0", "item1", "item2"]
List {
    ForEach(items, id: \.self) { item in
        ZStack {
            Rectangle()
                .frame(width: 150, height: 65)
            Text(item)
        }                            
        .onMove { from, to in
            items.move(fromOffsets: from, toOffset: to)
        }
    }
    .listRowSeparator(.hidden)
    .listRowBackground(Color.clear)      
}
.listStyle(.plain)
.scrollContentBackground(.hidden)

这个想法是将 ForEach 中的项目显示为在屏幕上“漂浮”,而不是以列表形式。

enter image description here

然而,当我拖动列表项(以便在列表中重新排序;使用.onMove()修饰符)时,会出现一个白色背景。

enter image description here

我想要移除这个背景,让它看起来就像是我只是在移动列表项(只有圆角矩形,没有背景)。我该如何实现这个效果?
编辑:我已经添加了.onDrag {} \@ViewBuilder preview: {},可以在拖动时移除背景,但是当我们长按项目并开始拖动之前,它仍然会出现。

如果有一种明确修改拖动时显示的预览的方法,我也想改变透明度。 - Nicolas Gimelli
1
这个问题看起来像是一个重复的问题,与这个相似。 - burnsi
1
@burnsi那个问题使用了onDrag,它允许预览闭包,而我正在使用onMove。 - Nicolas Gimelli
@burnsi是正确的。提供的链接本质上是同一个问题,回答是不可能的或者在iOS 16之后,可以通过.onDrag {}和@ViewBuilder preview: {}实现。 - Allan Garcia
你可以指定一个预览,但是当你长按列表项(就在开始拖动之前),列表项的背景仍然会出现。 - Nicolas Gimelli
2个回答

0
白色背景出现是因为在列表中默认的单元格选择类型,目前无法修改。
解决方法是使用ScrollView和LazyVStack的组合,并手动实现重新排序逻辑。
类似于这样的方式。
import SwiftUI
import UniformTypeIdentifiers

/// This represents a simple item in your list
struct ListItem: View {
    let item: String

    var body: some View {
        ZStack {
            Rectangle()
                .frame(width: 150, height: 65)
            Text(item)
        }
    }
}

/// We need custom NSItemProvider to track when the drop finishes
final class ListItemProvider: NSItemProvider {
    var onCompletion: (() -> ())?

    deinit {
       onCompletion?()
    }
}

/// ViewModel to handle the state changes
/// You can emit this and simply use @State objects in your view
/// as you have done in your example
final class ListViewModel: ObservableObject {
    @Published private(set) var items = ["item0", "item1", "item2"]
    @Published private(set) var draggedItem: String?

    func shouldHighlight(item: String) -> Bool {
        return draggedItem == item
    }

    func move(to item: String) {
        guard let draggedItem,
              let from = items.firstIndex(of: draggedItem),
              let to = items.firstIndex(of: item),
              item != draggedItem else { return }

        items.move(fromOffsets: IndexSet(integer: from), toOffset: to > from ? to + 1 : to)
    }

    func startInteraction(item: String) -> NSItemProvider {
        let provider = ListItemProvider(item: nil, typeIdentifier: item)
        provider.onCompletion = { [weak self] in
            DispatchQueue.main.async {
                self?.draggedItem = nil
            }
        }
        
        if draggedItem == nil && items.contains(item) {
            draggedItem = item
        }

        return provider
    }
}

/// Custom drop delegate to implement custom reordering logic
struct ListDropDelegate: DropDelegate {
    let item: String
    @ObservedObject var viewModel: ListViewModel

    func performDrop(info: DropInfo) -> Bool {
        viewModel.shouldHighlight(item: item)
    }

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

    func dropEntered(info: DropInfo) {
        withAnimation(.default) {
            self.viewModel.move(to: self.item)
        }
    }
}

/// And finally the view
struct ContentView: View {
    @StateObject var viewModel: ListViewModel = .init()

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(viewModel.items, id: \.self) { item in
                    ListItem(item: item)
                        .opacity(viewModel.shouldHighlight(item: item) ? 0 : 1)
                        .onDrag { viewModel.startInteraction(item: item) }
                        .onDrop(of: [.text], delegate: ListDropDelegate(item: item, viewModel: viewModel))
                }
            }
        }
    }
}

-5

另一种方法:使用节(这可能是重新排序时背景的原因)

var body: some View {
    List {
        ForEach(self.items, id: \.self) { item in
            Section {
                Text(item)
                    .padding(.all)
                    .listStyle(.plain)
                    .foregroundColor(.white)
                    .listRowBackground(Color.blue)
                    .cornerRadius(12)
            }
            .listSectionSeparator(.automatic)
            .listSectionSeparatorTint(.clear)
            .listStyle(.plain)
        }
        .onMove(perform: move)
    }
    .scrollContentBackground(.hidden)
}


func move(from source: IndexSet, to destination: Int) {
    items.move(fromOffsets: source, toOffset: destination)
}
  • 不需要使用ZStack,配置文本
  • 需要处理动画和边缘情况,但在重新排序时没有背景

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接