I tried to resolve it "natively" too, but it seems that dropPreviewParametersForRowAt is not called when you are doing drag&drop in the same table view.
Btw, dragPreviewParametersForRowAt is working fine I set something like:
func tableView(_ tableView: UITableView, dragPreviewParametersForRowAt indexPath: IndexPath) ->
UIDragPreviewParameters? {
let parameters = UIDragPreviewParameters()
parameters.backgroundColor = .appClear
if let cell = tableView.cellForRow(at: indexPath) {
parameters.visiblePath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: 10)
}
return parameters
}
and dragged cell was nicely rounded.
For the dropping preview, I went with custom view, like so:
final class DragDropHighlightView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
isHidden = true
layer.cornerRadius = 10
layer.borderColor = UIColor.appSystemBlue.cgColor
layer.borderWidth = 2
backgroundColor = .appSystemBlue.withAlphaComponent(0.1)
}
func setHighlighted(_ highlighted: Bool) {
isHidden = !highlighted
}
}
class AppTableViewCell: UITableViewCell {
// Your other UI and business logic properties...
//
//
private let dragDropHighlightView = DragDropHighlightView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
setupLayout()
// Your other setup methods
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayout()
}
// Public method for showing/hiding the highlight view
func setDragDropHighlighted(_ highlighted: Bool) {
dragDropHighlightView.setHighlighted(highlighted)
backgroundColor = highlighted ? .clear : .appSecondarySystemGroupedBackground
}
private func setupLayout() {
// Your other layout setup here
// Using TinyConstraints SDK for Auto Layout
// Pinning to the edges of the cell our highlight view
contentView.addSubview(dragDropHighlightView)
dragDropHighlightView.edgesToSuperview()
}
}
In the file where I have the Table View I have this property
private var highlightedCell: AppTableViewCell? {
didSet {
oldValue?.setDragDropHighlighted(false)
highlightedCell?.setDragDropHighlighted(true)
}
}
And at occasions where I need to deselect the cell, I set the property to nil and where I want to actually highlight the cell I set the property with the table cell type. For some more insights see below:
Hide at (highlightedCell = nil
)
func tableView(_ tableView: UITableView, dragSessionDidEnd session: any UIDragSession)
Hide at highlightedCell = nil
:
func tableView(_ tableView: UITableView, dropSessionDidExit session: any UIDropSession)
func tableView(_ tableView: UITableView, performDropWith coordinator: any UITableViewDropCoordinator)
Show at & Hide at:
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: any UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?)
UITableViewDropProposal(operation: .cancel)
expectedguard let indexPath = destinationIndexPathelse {
// Here I do some extra checks whether I am out of my model's array bounds
highlightedCell = nil
return UITableViewDropProposal(operation: .cancel)
}
// Highlight the destination cell
if let cell = tableView.cellForRow(at: indexPath) as? AppTableViewCell {
highlightedCell = cell
}
So the drop session update can look like this:
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: any UIDropSession, withDestinationIndexPath
destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
guard let indexPath = destinationIndexPath,
indexPath.section < tableSections.count, // Do the additional check in order to NOT move out of array and crash the app.
indexPath.row < tableSections[indexPath.section].cells.count else {
return cancelDropOperation()
}
let destinationCell = tableSections[indexPath.section].cells[indexPath.row]
// Check if source and destination are the same BUT
// ⚠️ WARNING Not working though. 🤷
if let dragItems = session.items.first,
let sourceFileCell = dragItems.localObject as? FilesCell,
sourceFileCell.fileURL == destinationFileCell.fileURL {
highlightedCell = nil
return UITableViewDropProposal(operation: .cancel)
}
// Highlight the destination cell
if let cell = tableView.cellForRow(at: indexPath) as? AppTableViewCell {
highlightedCell = cell
}
return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
}
⚠️ WARNING
What I was not able to figure out yet is that when you hover with dragged cell above itself, the highlight view will not disappear and will remain at the last "valid" indexPath, so it's not the best UX. I haven't came up with working logic yet how to compare indexPath of dragged cell with indexPath of "destination" cell.