如果在滑动期间快速点击 UITableView tableView(_:didEndEditingRowAt:) 则不会调用

Posted

技术标签:

【中文标题】如果在滑动期间快速点击 UITableView tableView(_:didEndEditingRowAt:) 则不会调用【英文标题】:UITableView tableView(_:didEndEditingRowAt:) not called if rapidly tapping during swipe 【发布时间】:2017-05-23 03:16:10 【问题描述】:

我有一个UITableView 和一个editAction,可以通过向左滑动访问。

似乎有一个内置的UITableView 功能,如果您滑动单元格以显示编辑操作,然后点击 tableView 中的任意位置,该操作会自动滑动关闭,并调用didEndEditingRowAt通知代理编辑结束。

但是,我看到的问题是,有时,如果您向左滑动,然后在滑动后非常快速地滑动(当只有一小部分编辑操作可见并且动画正在进行时),您点击任意位置否则在屏幕上,编辑操作已关闭,但不会调用 didEndEditingRowAt

因此,使用以下代码,我们最终使 tableView 处于编辑模式,但没有滑动打开任何视图,并且打印的最后一行是 Will Edit,确认从未调用过 didEndEditingRowAt

  class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate 

  let tableView = UITableView()

  override func viewDidLoad() 
    super.viewDidLoad()

    view = tableView
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "foo")
    tableView.delegate = self
    tableView.dataSource = self
    tableView.allowsSelectionDuringEditing = false
  

  func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? 
    let deleteAction = UITableViewRowAction(style: .normal, title: " Remove") (_, indexPath) in print("OK") 
    return [deleteAction]
  

  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 

  func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 

  func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? 
    return indexPath
  


  func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) 
    print("Will edit")
  

  func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) 
    print("Did end edit")
  

  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    return 80
  

  func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) 

  

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

    return tableView.dequeueReusableCell(withIdentifier: "foo") ?? UITableViewCell()
  

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    return 10
  

现在,这种情况只是有时会发生,而且要把握好时机有点困难,但它绝对是可重现的。

这里是整个演示的链接:https://github.com/gregkerzhner/SwipeBugDemo

我在这里做错了什么或期待错了吗?在我的真实项目中,我的代码使其他单元格淡化以专注于当前正在编辑的单元格,并且我最终处于其他单元格淡化的糟糕状态,但聚焦的单元格没有打开任何编辑操作。

【问题讨论】:

我很惊讶我能够在自己的项目中复制它,尽管只有十分之一的尝试。对我来说,这似乎是苹果公司的一个错误,我通常不愿意说。就个人而言,如果用户正常使用该应用程序,我认为他们不会故意这样做。 是的,可能与openradar.me/19411256有关 我想知道除了构建我自己的刷卡代码之外是否还有其他解决方法。否则这无法通过 QA! 【参考方案1】:

这绝对是 UITableView 中的一个错误,即使滑动弹回(例如,如果你只滑动一点),它也会保持在“编辑”状态。

好消息是您可以使用 UITableViewCell 的一些方法来找到解决方法。 UITableViewCell 有相应的方法来通知动作相关的状态变化:

func willTransition(to state: UITableViewCellStateMask)
func didTransition(to state: UITableViewCellStateMask)
func setEditing(_ editing: Bool, animated: Bool)
var showingDeleteConfirmation: Bool  get 

在过渡(和动画)开始和结束时调用过渡方法。当你滑动时,willTransitiondidTransition 将被调用(状态为.showingDeleteConfirmationMask),showingDeleteConfirmation 将是truesetEditing 也被称为 true

虽然这仍然存在问题(除非您实际打开按钮,否则单元格不应成功编辑),didTransition 是一个回调,您有机会检查操作视图是否确实可见。我认为没有任何可靠的方法可以做到这一点,但也许只需检查单元格的contentView 占据了它的大部分边界就足够了。

【讨论】:

我会接受这个作为答案,因为它似乎没有@ncke 的建议那么老套 @gregkerzhner 在你的边缘情况下,这些回调中的哪些成功通过了?恐怕他们会和 didEndEditing 进入同一个黑洞……【参考方案2】:

所以最后,工作解决方案与@ncke 和@hybridcattt 建议的有点不同。

@ncke 解决方案的问题在于,在此滑动/点击交互过程中不会调用 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView),因此永远不会调用解决方法。

@hybridcattt 的解决方案的问题是那些 UITableViewCell 回调被调用得太早,所以如果你执行轻扫快速点击动作,当所有这些回调得到时,UITableViewCellDeleteConfirmationView 仍然是单元格子视图的一部分调用。

最好的办法似乎是重写UITableViewCellwillRemoveSubview(_ subview: UIView) 函数。每次删除 UITableViewCellDeleteConfirmationView 时都会可靠地调用它,无论是在正常滑动关闭期间,还是在这种有问题的快速滑动场景中。

  protocol BugFixDelegate 
    func editingEnded(cell: UITableViewCell)
  

  class CustomCell: UITableViewCell 
    weak var bugFixDelegate: BugFixDelegate?
    override func willRemoveSubview(_ subview: UIView) 
      guard String(describing: type(of: subview)) == "UITableViewCellDeleteConfirmationView" else return 
      endEditing(true)
      bugFixDelegate.editingEnded(cell: self)
    
  

正如@hybridcattt 和@ncke 所建议的那样,在您的控制器中,您可以连接到这个委托并将丢失的事件发送到UITableViewUITableViewDelegate 就像

class DummyController: UIViewController 
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    guard let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as? CustomCell else return UITableViewCell()

    cell.bugFixDelegate = self

  


extension DummyController: BugFixDelegate 
  //do all the missing stuff that was supposed to happen automatically
  func editingEnded(cell: UITableViewCell) 
    guard let indexPath = self.tableView.indexPath(for: cell) else return
    self.tableView.setEditing(false, animated: false)
    self.tableView.delegate?.tableView?(tableView, didEndEditingRowAt: indexPath)
  

【讨论】:

很好的提示,谢谢!就我而言,使用tableView.setEditing(false, animated: false) 结束对tableView 的编辑就足够了。这也叫tableView: didEndEditingRowAt indexPath:,一切都很好。【参考方案3】:

我并不是说这是有史以来最好的事情,但如果您想要一个解决方法,这可能是一个开始:

extension UITableView 

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) 

        for cell in self.visibleCells 
            if cell.isEditing && (cell.subviews.count < 3 || cell.subviews[2].frame.origin.x < 30.0) 

                print("\(cell) is no longer editing")
                cell.endEditing(true)
                if let indexPath = self.indexPath(for: cell) 
                    self.delegate?.tableView?(self, didEndEditingRowAt: indexPath)
                
            
        
    


这个想法是 UITableView 是 UIScrollView 的子类。虽然前者的委托方法似乎坏了,但后者的仍在被调用。一些实验产生了这个测试。

只是一个想法 :) 您可能更喜欢继承 UITableView 而不是扩展,以本地化 hack。

【讨论】:

【参考方案4】:

这是我的解决方案。享受吧。

func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle 
    if #available(ios 10.0, *) 
    return .none;
     else 
        return .delete;
    

【讨论】:

以上是关于如果在滑动期间快速点击 UITableView tableView(_:didEndEditingRowAt:) 则不会调用的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 禁用滑动以快速删除特定单元格

保存 UITableView 单元格附件的状态?

iOS UITableView横向滑动点击UIButton放大

试图使 UITableViewCell 可滑动和可点击,同时保持 UITableView 可滚动,为啥我的单元格滑动有时只能工作?

UITableView 以编程方式调用滑动操作

UITableView以编程方式调用滑动操作