UICollectionView 在多个部分之间拖放单元格

问题描述 投票:0回答:0

我正在学习 UICollectionView,我正在处理一个 collectionview 中各部分之间的单元格拖放操作

当一个部分中的元素数量多于部分的总数时,我遇到了问题

Fatal error: Index out of range

我不知道我是否正确声明了二维数组,谁能帮我解决代码的正确部分?

ViewController 中的代码

class ExampleViewController: UIViewController {
  @IBOutlet var collectionView: UICollectionView!

  private let minimumLineSpacing: CGFloat = 1
  private let minimumInteritemSpacing: CGFloat = 1
  private let numberOfItemInRow: CGFloat = 3

  private var numbers: [[Int]] = [
    [Int](1...9),
    [Int](2...3),
    [Int](1...2),
    [Int](4...7),
    [Int](5...9),
    [Int](3...5),
  ]

  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.

    configCollectionView(collectionView)
    print(numbers)

    // uncomment to avoid conflict with Long Tap handler
//    configDragDropGesture()

    configDoubleTapGesture()

    configLongTapGesture()
  }
}

// MARK: - configCollectionView ExampleViewController

extension ExampleViewController {
  func configCollectionView(_ collectionView: UICollectionView) {
    collectionView.register(
      .init(nibName: "\(ExampleCell.self)", bundle: nil),
      forCellWithReuseIdentifier: "\(ExampleCell.self)")

    collectionView.register(
      .init(nibName: "\(ExampleHeaderReuseableView.self)", bundle: nil),
      forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
      withReuseIdentifier: "\(ExampleHeaderReuseableView.self)")

    collectionView.register(
      .init(nibName: "\(ExampleFooterReusableView.self)", bundle: nil),
      forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,
      withReuseIdentifier: "\(ExampleFooterReusableView.self)")

    collectionView.delegate = self
    collectionView.dataSource = self

    collectionView.dragDelegate = self
    collectionView.dropDelegate = self

    collectionView.reorderingCadence = .fast
    collectionView.dragInteractionEnabled = true
  }
}

// MARK: - UICollectionViewDragDelegate

extension ExampleViewController: UICollectionViewDragDelegate {
  func collectionView(_ collectionView: UICollectionView,
                      itemsForBeginning session: UIDragSession,
                      at indexPath: IndexPath) -> [UIDragItem]
  {
    let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
    let dragItem = UIDragItem(itemProvider: itemProvider)
    dragItem.localObject = numbers[indexPath.section][indexPath.row]

    print(Self.self, #function)

    return [dragItem]
  }
}

// MARK: - UICollectionViewDropDelegate

extension ExampleViewController: UICollectionViewDropDelegate {
  func collectionView(_ collectionView: UICollectionView,
                      dropSessionDidUpdate session: UIDropSession,
                      withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
  {
    if collectionView.hasActiveDrag {
      return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }
    return UICollectionViewDropProposal(operation: .forbidden)
  }

  func collectionView(_ collectionView: UICollectionView,
                      performDropWith coordinator: UICollectionViewDropCoordinator)
  {
    var destinationIndexPath: IndexPath
    if let indexPath = coordinator.destinationIndexPath {
      destinationIndexPath = indexPath
    } else {
      let row = collectionView.numberOfItems(inSection: 0)
      destinationIndexPath = IndexPath(item: row - 1, section: 0)
    }

    if coordinator.proposal.operation == .move {
      reOrderItems(collectionView: collectionView,
                   coordinator: coordinator,
                   destinationIndexPath: destinationIndexPath)
    }
  }
}

// MARK: - ExampleViewController reOrderItems

extension ExampleViewController {
  func reOrderItems(collectionView: UICollectionView,
                    coordinator: UICollectionViewDropCoordinator,
                    destinationIndexPath: IndexPath)
  {
    if let item = coordinator.items.first,
       let sourceIndexPath = item.sourceIndexPath
    {
      collectionView.performBatchUpdates({
        print("origin arr: ", numbers)
        self.numbers[sourceIndexPath.section].remove(at: sourceIndexPath.item)
        print("after remove: ", numbers)
        self.numbers[destinationIndexPath.section].insert((item.dragItem.localObject as? Int)!,
                                                          at: destinationIndexPath.item)
        print("after insert: ", numbers)

        collectionView.deleteItems(at: [sourceIndexPath])
        collectionView.insertItems(at: [destinationIndexPath])
      }, completion: nil)
      coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
    }
  }
}

// MARK: - UICollectionViewDelegate

extension ExampleViewController: UICollectionViewDelegate {
  // Handle Tap Action
  func collectionView(_ collectionView: UICollectionView,
                      didSelectItemAt indexPath: IndexPath)
  {
    presentAlert(withTitle: "Single Tap",
                 message: "You tapped at index: \(indexPath.row)",
                 actionTitle: "OK",
                 style: .default)
  }

  // Handle Header - Footer

  func collectionView(_ collectionView: UICollectionView,
                      viewForSupplementaryElementOfKind kind: String,
                      at indexPath: IndexPath) -> UICollectionReusableView
  {
    switch kind {
    case UICollectionView.elementKindSectionHeader:

      if let headerView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: "\(ExampleHeaderReuseableView.self)",
        for: indexPath) as? ExampleHeaderReuseableView
      {
        headerView.backgroundColor = .magenta
        headerView.bindData(indexPath)

        return headerView
      }

    case UICollectionView.elementKindSectionFooter:

      if let footerView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: "\(ExampleFooterReusableView.self)",
        for: indexPath) as? ExampleFooterReusableView
      {
        footerView.backgroundColor = .cyan
        footerView.bindData(indexPath)

        return footerView
      }

    default:
      return UICollectionReusableView()
    }

    return UICollectionReusableView()
  }
}

// MARK: - UICollectionViewDataSource

extension ExampleViewController: UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
    print(Self.self, #function)
    return true
  }

  func collectionView(_ collectionView: UICollectionView,
                      moveItemAt sourceIndexPath: IndexPath,
                      to destinationIndexPath: IndexPath)
  {
    let item = numbers.remove(at: sourceIndexPath.row)
    numbers.insert(item, at: destinationIndexPath.row)
    print(Self.self, #function)
  }

  func numberOfSections(in collectionView: UICollectionView) -> Int {
    return numbers.count
  }

  func collectionView(_ collectionView: UICollectionView,
                      numberOfItemsInSection section: Int) -> Int
  {
    return numbers[section].count
  }

  func collectionView(_ collectionView: UICollectionView,
                      cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
  {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(ExampleCell.self)",
                                                  for: indexPath) as! ExampleCell

    cell.bindData(numbers[indexPath.row], indexPath)

    return cell
  }
}

文件单元格中的代码

class ExampleCell: UICollectionViewCell {

  @IBOutlet weak var titleLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
}

// MARK: - Func bindData ExampleCell

extension ExampleCell {
  func bindData(_ number: [Int],_ indexPath: IndexPath) {
    titleLabel.text = "\(number) - \(indexPath.section)"
  }

}

我正在使用 indexpath 在 collectionview 上显示

swift uicollectionview drag-and-drop uikit uicollectionviewcell
© www.soinside.com 2019 - 2024. All rights reserved.