You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NSTableView取消拖拽后,后续拖拽获取rowIndex异常为-1

NSTableView拖拽后再次拖拽时rowIndex返回-1的问题

问题现象

  • 实现NSTableView内部拖拽功能正常,但将元素拖拽到表格边界外取消拖拽后,后续再次尝试拖拽时,tableView(_:draggingSession:willBeginAt:forRowIndexes:)方法中通过屏幕点转换得到的rowIndex为-1,首次拖拽时能正常获取有效rowIndex,且表格尺寸和行数未发生变化。
  • 拖拽到边界外时,validateDrop返回空NSDragOperation,元素动画回到原位置,表格显示正常,但后续拖拽失效。

问题原因分析

当拖拽操作在表格外取消时,draggingDestinationFeedbackStyle被设置为.gap后没有正确重置——虽然在acceptDrop中重置了该属性,但拖拽取消时并不会调用acceptDrop,导致.gap状态被保留,干扰了后续拖拽会话的坐标转换逻辑,进而无法正确识别拖拽起始行。


解决方案

  1. 在拖拽会话结束的回调中,无论操作成功与否,强制重置表格的拖拽反馈样式为.regular,确保状态回归正常。
  2. 直接使用拖拽会话回调中传入的rowIndexes参数获取起始行索引,替代屏幕点转换的方式,避免坐标计算误差。

修改后的关键代码

1. 修正拖拽会话结束时的状态重置

public func tableView( _ tableView: NSTableView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint,operation: NSDragOperation)  {
    // 无论拖拽成功与否,都重置拖拽反馈样式
    tableView.draggingDestinationFeedbackStyle = .regular
    
    guard let windowPoint = tableView.window?.convertPoint(fromScreen: screenPoint) else { return }
    let tableViewPoint = tableView.convert(windowPoint, from: nil)
    let rowIndex = tableView.row(at: tableViewPoint)
  
    print("Drag Ended at row: \(rowIndex)")
    
    if !self.tableView.bounds.contains(tableViewPoint) {
        print("Dropped Outside")
    }
}

2. 使用传入的rowIndexes替代坐标转换获取行索引

public func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forRowIndexes rowIndexes: IndexSet) {
    // 直接使用参数中的rowIndexes获取行索引,无需坐标转换
    guard let rowIndex = rowIndexes.first else {
        print("ERROR: No valid row index")
        return
    }
    
    let rows = self.tableView.numberOfRows
    print("DRAGGING WILL BEGIN AT row: \(rowIndex)")
    print("tableView has \(rows) rows -> bounds: \(self.tableView.bounds)")
    
    // 后续逻辑保持不变,使用rowIndex进行操作
    guard let cellView = tableView.view(atColumn: 0, row: rowIndex, makeIfNecessary: true) else { 
        print("ERROR: No cellView - 98264r") 
        return 
    }

    let xOffset = cellView.frame.origin.x
    
    session.enumerateDraggingItems(options: NSDraggingItemEnumerationOptions.concurrent, for: nil, classes: [NSPasteboardItem.self], searchOptions: [:]) { (item, index, _) in
        // ... 原有代码保持不变
    }
}

3. 优化validateDrop的判断逻辑(可选)

确保只有在拖拽源是当前表格且操作合法时才设置.gap,避免不必要的状态变更:

public func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
    guard dropOperation == .above, info.draggingSource as? NSTableView === self.tableView else {
        return []
    }
    tableView.draggingDestinationFeedbackStyle = .gap
    return .move
}

内容的提问来源于stack exchange,提问作者Marco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 01:45:53