79498294

Date: 2025-03-10 14:31:37
Score: 0.5
Natty:
Report link

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:

Custom Highlight View
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
    }
}

Laying out Highlight View in Table Cell
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()
    }
}

My Logic when to show/hide DragDropHighlightView

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:

☝️ UITableViewDragDelegate

Hide at (highlightedCell = nil)

func tableView(_ tableView: UITableView, dragSessionDidEnd session: any UIDragSession)

🎯 UITableViewDropDelegate

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

guard 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.

Reasons:
  • Blacklisted phrase (0.5): I need
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (0.5):
Posted by: peetadelic