【问题标题】:UITableView re-order last row removed (framework bug or working as intended ?)UITableView 重新排序删除的最后一行(框架错误或按预期工作?)
【发布时间】:2021-10-13 05:24:52
【问题描述】:

我认为这可能是一个框架错误,我将其简化为一个极其简单和静态的示例(代码如下),具体情况如下:

  • 允许将行重新排序到除最后一个位置之外的任何位置
  • 如果用户试图将其放到最后一行,建议退回到原来的位置

这里是触发错误的方法:

  • 取第一行之一并重新排序 - 一直拖到最后一行(不允许用户放下它),继续拖动它,但将其向上拖动并放在允许的位置位置,同时仍然可以查看最后一行。重要的是,要放置的行是屏幕外的行(原始位置在最终放置位置不可见)
  • 最后一行将从视图中消失
  • 最后一行没有调用didEndDisplaying cell
  • 上下滚动会导致错误的视图状态,空视图会出现在“某处”
  • 多滚动一下最终会再次解决问题

我不认为这是按预期工作的,但是通过更多测试,我怀疑从 proposedDestinationIndexPath 返回的 indexPaths 必须是单调的,例如如果拖动索引 4 并返回 5,6...14 之前,您将无法返回到 sourceIndex (4),但您必须锁定到允许的最高索引(本例中为 14)

我很好奇是否有人对此有详细信息或任何官方信息。什么是允许的,什么是不允许的?

class TestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    private let tableView = UITableView()
    private var rows: [RowData] = [
        RowData("1"),
        RowData("2"),
        RowData("3"),
        RowData("4"),
        RowData("5"),
        RowData("6"),
        RowData("7"),
        RowData("8"),
        RowData("9"),
        RowData("10"),
        RowData("11"),
        RowData("12"),
        RowData("13"),
        RowData("14"),
        RowData("15")
    ]
    
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.setNavigationBarHidden(false, animated: false)
        // ADVANCED VIEW
        tableView.backgroundColor = .systemBlue
        tableView.rowHeight = 120
        tableView.showsVerticalScrollIndicator = false
        tableView.showsHorizontalScrollIndicator = false
        tableView.separatorStyle = .none
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        //
        tableView.register(SpyCell.self, forCellReuseIdentifier: "default_row")
        tableView.delegate = self
        tableView.dataSource = self
        tableView.setEditing(true, animated: false)
//        tableView.allowsSelectionDuringEditing = false
//        tableView.allowsMultipleSelectionDuringEditing = false
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return rows.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return tableView.dequeueReusableCell(withIdentifier: "default_row", for: indexPath)
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cell.textLabel?.text = rows[indexPath.row].title
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120
    }
    
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120
    }
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
//    func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
//        false
//    }
    
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        return .none
    }
    
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let removed = self.rows.remove(at: sourceIndexPath.row)
        self.rows.insert(removed, at: destinationIndexPath.row)
        // uncomment to confirm data is properly inserted
//        for row in self.rows {
//            print("row: \(row)")
//        }
    }
    
    func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
        if proposedDestinationIndexPath.row >= rows.count - 1 {
            return sourceIndexPath
            // BELOW WORKS as expected and doesn't cause the error
//            return IndexPath(row: rows.count - 2, section: 0)
        } else {
            return proposedDestinationIndexPath
        }
    }
    
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        print("didEndDisplaying cell : \(cell.textLabel?.text ?? "unexpected")")
    }
}

class RowData {
    let title: String
    
    init(_ title: String) {
        self.title = title
    }
}

class SpyCell: UITableViewCell {
    
    override func removeFromSuperview() {
        print("removeFromSuperview() \(textLabel?.text ?? "unexpected")")
        super.removeFromSuperview()
    }
}

【问题讨论】:

  • 这个使用 willDisplay 和 didEndDisplaying 是不是有问题?如果您删除这些并正确执行此操作会发生什么?
  • @matt 我不认为使用它们有什么问题。但是我将它们都删除了进行测试并直接在cellForRowAt中设置了textLabel,但没有任何改变。

标签: ios swift uitableview uikit


【解决方案1】:

我认为问题是由于尝试移除并插入相同的位置。如果您将 moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath 函数更改为在源和目标相同时不移动行,则不会看到错误。

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    if sourceIndexPath.row != destinationIndexPath.row {
        let removed = self.rows.remove(at: sourceIndexPath.row)
        self.rows.insert(removed, at: destinationIndexPath.row)
    }
}

编辑:这是一个包含此修复程序的视频,我没有看到您描述的错误:https://imgur.com/a/EHPsXmj

【讨论】:

  • 我试过了,但不幸的是问题仍然存在。我还对其进行了调试,并且仅在删除行后才调用 moveRowTo。 (通常不是同一行)。记得触发 bug: 1. 拖动的行必须是屏幕外的行 2. 你必须将它拖到最后一行,然后再向上移动,然后再放开
  • 检查我的编辑,我添加了一个带有修复程序的视频,它是否正常工作?
  • @clawseome 不,一直往下拖后,再往上拖,把视图放在 13 到 14 之间,15 号就会消失。附言感谢您为此付出了如此多的努力!
  • @Su-AuHwang 您能否澄清一下您只想阻止用户将单元格移动到最后一行?例如,如果有 15 个单元格,他们只能删除单元格,所以它是第 14 个或更低?因为您的代码看起来会阻止他们将单元格放到最后一个位置和倒数第二个位置
  • 这只是一个例子,重要的触发bug只是最后一行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-13
  • 1970-01-01
  • 2015-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多