使用NSFetchedResultsController和CoreData,UITableView意外地与beginUpdates()/ endUpdates()/ performBatchUpdates()反弹

时间:2018-06-22 05:55:56

标签: ios swift uitableview core-data nsfetchedresultscontroller

当行数填满视图时,

UITableView使用beginUpdates()和CoreData意外地以endUpdates() / performBatchUpdates() / NSFetchedResultsController反弹。 复制非常简单。 -从主从应用程序模板(带有CoreData)创建一个新项目。 -在情节提要中,删除“ showDetail”设置。 (我们不需要详细视图) -在MasterViewController中,将segue func prepare()替换为:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {  
    let event = fetchedResultsController.object(at: indexPath)  
    let timestamp = event.timestamp  
    event.timestamp = timestamp // The idea is to simply update the Event entity.  
  }

启动该应用程序(在iOS设备或模拟器中),并添加足够的行以填充视图(在iPhone SE中为11行)。 向下滚动视图,然后选择任何行。该视图将快速上下跳动。 那是一个错误,还是代码有问题?

4 个答案:

答案 0 :(得分:2)

好的,我可能已经找到了解决方案,请告诉我您的想法。 想法是在insert/delete/move中处理performBatchUpdates,而将update排除在外。 所以我创建了这个枚举和属性:

enum FetchedResultsChange<Object> {
  case insert(IndexPath)
  case delete(IndexPath)
  case move(IndexPath, IndexPath, Object)
}
var fetchedResultsChanges: [FetchedResultsChange<Event>] = []

controllerWillChangeContent为空:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {}

didChange变为:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
    case .insert:
      self.fetchedResultsChanges.append(.insert(newIndexPath!))
    case .delete:
      self.fetchedResultsChanges.append(.delete(indexPath!))
    case .update:
      configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event) // So this stays untouched.
    case .move:
      self.fetchedResultsChanges.append(.move(indexPath!, newIndexPath!, anObject as! Event))
    }
  }

controllerDidChangeContent变为:

  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard self.fetchedResultsChanges.count > 0 else { return }

    tableView.performBatchUpdates({
      repeat {
        let change = self.fetchedResultsChanges.removeFirst()
        switch change {
        case .insert(let newIndexPath):
          tableView.insertRows(at: [newIndexPath], with: .fade)
        case .delete(let indexPath):
          tableView.deleteRows(at: [indexPath], with: .fade)
        case .move(let indexPath, let newIndexPath, let event):
          configureCell(tableView.cellForRow(at: indexPath)!, withEvent: event)
          tableView.moveRow(at: indexPath, to: newIndexPath)
        }
      } while self.fetchedResultsChanges.count > 0
    }, completion: nil)
  }

那你怎么看?

答案 1 :(得分:0)

我在UITableView unexpectedly bounces with beginUpdates()/endUpdates()/performBatchUpdates()上注意到了类似的问题(重复?)

我在那里添加了关于使用表视图的estimatedHeightFor...方法的答案。实现这些方法以返回正数可解决表视图批更新期间的奇数跳动问题。

答案 2 :(得分:0)

更精致的解决方案是

  lazy var sectionChanges = [() -> Void]()
  lazy var objectChanges = [() -> Void]()

  func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard controller == self._fetchedResultsController else { return }
    self.sectionChanges.removeAll()
    self.objectChanges.removeAll()
  }

  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    guard controller == self._fetchedResultsController else { return }
    let sections = IndexSet(integer: sectionIndex)
    self.sectionChanges.append { [unowned self] in
      switch type {
      case .insert: self.tableView.insertSections(sections, with: .fade)
      case .delete: self.tableView.deleteSections(sections, with: .fade)
      default: break
      }
    }
  }

  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    guard controller == self._fetchedResultsController else { return }
    switch type {
    case .insert:
      if let verifiedNewIndexPath = newIndexPath {
        self.objectChanges.append { [unowned self] in
          self.tableView.insertRows(at: [verifiedNewIndexPath], with: .fade)
        }
      }
    case .delete:
      if let verifiedIndexPath = indexPath {
        self.objectChanges.append { [unowned self] in
          self.tableView.deleteRows(at: [verifiedIndexPath], with: .fade)
        }
      }
    case .update:
      if let verifiedIndexPath = indexPath, let event = anObject as? Event, let cell = self.tableView.cellForRow(at: verifiedIndexPath) {
        self.configureCell(cell, withEvent: event)
      }
    case .move:
      if let verifiedIndexPath = indexPath, let verifiedNewIndexPath = newIndexPath, let event = anObject as? Event, let cell = self.tableView.cellForRow(at: verifiedIndexPath) {
        self.configureCell(cell, withEvent: event)
        self.objectChanges.append { [unowned self] in
          self.tableView.moveRow(at: verifiedIndexPath, to: verifiedNewIndexPath)
        }
      }
    default: break
    }
  }

  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard controller == self._fetchedResultsController else { return }
    guard self.objectChanges.count > 0 || self.sectionChanges.count > 0 else { return }
    self.tableView.performBatchUpdates({[weak self] in
      self?.objectChanges.forEach { $0() }
      self?.sectionChanges.forEach { $0() }
    }) { (finished) in
      // here I check if the tableView is empty. If so, I usually add a label saying "no item, click add button to add items."
      // If not, then I remove this label.
    }
  }

答案 3 :(得分:-2)

这可能有帮助-

UIView.performWithoutAnimation {
                                self.tableView?.beginUpdates()
                                let contentOffset = self.tableView?.contentOffset
                                self.tableView?.reloadRows(at: [IndexPath(row: j, section: 0)], with: .automatic)
                                self.tableView?.setContentOffset(contentOffset!, animated: false)
                                self.tableView?.endUpdates()
                            }