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

How to remove the background of list items when dragging (and when long-pressing) with onMove in SwiftUI?

提问人:Nicolas Gimelli 提问时间:1/31/2023 最后编辑:Nicolas Gimelli 更新时间:8/26/2023 访问量:702

问:

我在 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: {}

iOS Swift 列表 SwiftUI

评论

0赞 Nicolas Gimelli 1/31/2023
如果有一种方法可以显式修改拖动时显示的预览,我还想更改不透明度。
1赞 burnsi 2/3/2023
这个问题似乎是重复的。
1赞 Nicolas Gimelli 2/3/2023
@burnsi这个问题使用 onDrag,它允许预览关闭,而我使用的是 onMove
0赞 Allan Garcia 2/6/2023
@burnsi是对的。提供的链接本质上是同一个问题,回答为不可能,或者在 iOS 16 之后,可能使用 .onDrag {} \@ViewBuilder 预览:{}
0赞 Nicolas Gimelli 8/26/2023
您可以指定预览,但是当您长按列表项时(在开始拖动之前),列表项背景仍将显示。

答:

-5赞 HelloWorld 2/7/2023 #1

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

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,配置文本
  • 需要处理动画和边缘情况,但没有背景温重新排序
0赞 narek.sv 8/26/2023 #2

出现白色背景,因为 中的默认单元格选择类型,很遗憾,目前无法修改。List

解决方法是手动使用和组合并实现重新排序逻辑ScrollViewLazyVStack

像这样的东西

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