UICollectionViewDropDelegate и DiffableDataSource - PullRequest
0 голосов
/ 12 января 2020

Я пытаюсь реализовать UICollectionViewDropDelegate с DiffableDataSource (представлен в iOS 13).

  • Я реализовал UICollectionViewDragDelegate, что прекрасно работает.
  • Я установил collectionView.dropDelegate = self
  • Я реализовал func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) (вы даже можете попробовать его с пустым методом).

Приложение будет собираться и запускаться, но как только когда вы начинаете перетаскивать ячейку, приложение немедленно завершает работу с этим сообщением:

Завершение приложения из-за необработанного исключения «NSInternalInconsistencyException», причина: «UICollectionView должен обновляться через UICollectionViewDiffableDataSource API, если он действует как источник данных UICollectionView. : пожалуйста, не вызывайте API-интерфейсы мутации непосредственно в UICollectionView.

Так что, похоже, этот метод пытается напрямую изменить UICollectionView (может быть, для перемещения ячеек по мере перемещения элемента?) Я могу ' не могу понять, как обойти это поведение.

Мысли?

Ответы [ 3 ]

0 голосов
/ 20 марта 2020

Если вы делаете изменения с collectionView.performBatchUpdates () для обновления вашего collectionView, это будет sh, кажется, вам нужно создать моментальный снимок на вашем источнике данных (UICollectionViewDiffableDataSource), который, как вы можете прочитать, указан в сообщении об ошибке: «API UICollectionViewDiffableDataSource при работе в качестве источника данных UICollectionView: пожалуйста, не вызывайте API мутации непосредственно в UICollectionView»

Попробуйте:

// MARK: - Properties
  var dataSource: UICollectionViewDiffableDataSource<Int, UIImage>?

  var entry: Entry? {
    didSet {
      guard let entry = entry else { return }
      let dateFormatter = DateFormatter()
      dateFormatter.setLocalizedDateFormatFromTemplate("MMM dd yyyy, hh:mm")
      title = dateFormatter.string(from: entry.dateCreated)
    }
  }

, где ваша запись представляет собой структуру:

struct Entry {
  let id = UUID().uuidString
  let dateCreated = Date()
  var log: String?
  var images: [UIImage] = []
  var isFavorite: Bool = false
}

extension Entry: Hashable {
  func hash(into hasher: inout Hasher) {
    hasher.combine(dateCreated)
    hasher.combine(log)
  }

  static func == (lhs: Entry, rhs: Entry) -> Bool {
    return lhs.dateCreated == rhs.dateCreated &&
      lhs.log ?? "" == rhs.log ?? "" &&
      lhs.images == rhs.images &&
      lhs.isFavorite == rhs.isFavorite
  }
}

И reloadSnapshot:

private func reloadSnapshot(animated: Bool) {
    var snapshot = NSDiffableDataSourceSnapshot<Int, UIImage>()
    snapshot.appendSections([0])
    snapshot.appendItems(entry?.images ?? [])
    dataSource?.apply(snapshot, animatingDifferences: animated)
  }

Наконец, на вашем UICollectionViewDropDelegate:

func collectionView( _ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {

let destinationIndex = coordinator.destinationIndexPath?.item ?? 0

for item in coordinator.items {
  if coordinator.session.localDragSession != nil,
    let sourceIndex = item.sourceIndexPath?.item {

    self.entry?.images.remove(at: sourceIndex)
  }

  item.dragItem.itemProvider.loadObject(ofClass: UIImage.self) {
    (object, error) in
    guard let image = object as? UIImage, error == nil else {
      print(error ?? "Error: object is not UIImage")
      return
    }
    DispatchQueue.main.async {
      self.entry?.images.insert(image, at: destinationIndex)
      self.reloadSnapshot(animated: true)
    }
  }
}
}

Я все это основываюсь на чтении и работе с raywenderlich.com "Catalyst by Tutorials «

0 голосов
/ 06 апреля 2020

Еще одна вещь:

Если вы используете метод UICollectionViewDropDelegate для возврата UICollectionViewDropProposal, collectionView(_:dropSessionDidUpdate:withDestinationIndexPath destinationIndexPath:), этот метод будет вызывать недифференцируемые методы под капотом, когда он анимирует падение. .

Полагаю, Apple никогда не проверяла это вместе с источниками данных, которые можно было бы использовать. Вам нужно будет удалить этот метод и самостоятельно реализовать его анимацию.

0 голосов
/ 13 марта 2020

Это довольно легко решить. Начиная с функции UICollectionViewDropDelegate executeDropWith. Обратите внимание, что мне нужны были только .move и нет .copy, но вы бы просто добавили метод copyItems (), аналогичный методу reorderItems () ниже:

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
   var destinationIndexPath: IndexPath
   if let indexPath = coordinator.destinationIndexPath {
      destinationIndexPath = indexPath
   } else {
      let section = collectionView.numberOfSections - 1
      let row = collectionView.numberOfItems(inSection: section)
      destinationIndexPath = IndexPath(row: row, section: section)
   }
   switch coordinator.proposal.operation {
   case .move:
      self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
      break
   case .copy:
      // Not copying between collections so this block not needed.
      return
   default:
      return
   }
}

Тогда у нас есть функция reorderItems (), которая нам нужна обрабатывать фактические изменения в collectionView. Следует пояснить одну вещь: оба снимка (NSDiffableDataSourceSnapshot <>) и dataSource (UICollectionViewDiffableDataSource <>) являются переменными класса.

/// reorderItems method
/// This method moves a cell from the sourceIndexPath to the destinationIndexPath within the same UICollectionView
///
/// - Parameters:
///   - coordinator: UICollectionViewDropCoordinator obtained in performDropWith
///   - destinationIndexPath: IndexPath where user dropped the element.
///   - collectionView: UICollectionView object where reordering is done.
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
   let items = coordinator.items
   if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
      var destIndexPath = destinationIndexPath
      if destIndexPath.row >= collectionView.numberOfItems(inSection: destIndexPath.section) {
         destIndexPath.row = collectionView.numberOfItems(inSection: destIndexPath.section) - 1
      }

      /// Since my collectionView data is attributed to a Firebase.storage set of data, this is where I write my changes back to the store.

      snapshot.moveItem(dataSource.itemIdentifier(for: sourceIndexPath)!, beforeItem: dataSource.itemIdentifier(for: destinationIndexPath)!)
      dataSource.apply(snapshot, animatingDifference: true)
      coordinator.drop(items.first!.dragItem, toItemAt: destIndexPath)
   }
}
...