Переупорядочение UICollectionViewController не работает при вставке в виде контейнера

Переупорядочение работает в iOS9, когда я добавляю это в мой подкласс UICollectionViewController

override func collectionView(collectionView: UICollectionView, 
        moveItemAtIndexPath sourceIndexPath: NSIndexPath, 
           toIndexPath destinationIndexPath: NSIndexPath)

Это не работает, если этот подкласс UICollectionViewController встроен в представление контейнера.

Я сделал демо-версию проблемы здесь

Любые идеи о том, почему и как их исправить?

Ответ 1

Проблема заключается в том, что вы помещаете UICollectionView внутри UIContainerView, который находится внутри UIViewController. Для выполнения UICollectionView требуется еще несколько шагов, чтобы работать как ожидалось.

Добавьте в ViewDidLoad в свой CollectionViewController следующее:

    self.collectionView!.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: "handleLongGesture:"))

Затем добавьте следующую функцию в свой CollectionViewController:

    func handleLongGesture(gesture: UILongPressGestureRecognizer)
    {

    switch(gesture.state)
    {

    case UIGestureRecognizerState.Began:
        guard let selectedIndexPath = self.collectionView!.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else
        {
            break
        }
        collectionView!.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
    case UIGestureRecognizerState.Changed:
        collectionView!.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
    case UIGestureRecognizerState.Ended:
        collectionView!.endInteractiveMovement()
    default:
        collectionView!.cancelInteractiveMovement()
    }
}

Наконец, обязательно включите следующее, чтобы убедиться, что вы правильно обрабатываете источник данных:

    override func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath,toIndexPath destinationIndexPath: NSIndexPath) {
    // Swap the values of the source and destination
}

Подробнее об этом ссылка.

Надеюсь, это помогло вам.

Ответ 2

Скутер отвечает правильно! Вот версия синтаксиса Swift 3:

import UIKit

class ViewController: UIViewController
{
    // MARK: - IBOutlets
    @IBOutlet weak var uiCollectionView: UICollectionView!

    // MARK: - Lifecycle
    override func viewDidLoad()
    {
        super.viewDidLoad()

        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.handleLongGesture))
        self.uiCollectionView.addGestureRecognizer(longPressGesture)
    }


    // MARK: - Gesture recogniser
    @objc func handleLongGesture(gesture: UILongPressGestureRecognizer)
    {
        switch(gesture.state)
        {

        case .began:
            guard let selectedIndexPath = self.uiCollectionView.indexPathForItem(at: gesture.location(in: self.uiCollectionView)) else
            {
                break
            }

            self.uiCollectionView.beginInteractiveMovementForItem(at: selectedIndexPath)

        case .changed:
            self.uiCollectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))

        case .ended:
            self.uiCollectionView.endInteractiveMovement()

        default:
            self.uiCollectionView.cancelInteractiveMovement()
        }
    }
}


// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource
{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        // TODO: Link to your data model
        return 20
    }


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
        // TODO: Link to your data model
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath)
        return cell
    }


    func collectionView(_ collectionView: UICollectionView,
                        moveItemAt sourceIndexPath: IndexPath,
                        to destinationIndexPath: IndexPath)
    {
        // TODO: Update your data model to reflect the change
    }
}


// MARK: - UICollectionViewDelegate
extension ViewController: UICollectionViewDelegate
{
    // TODO: Add any UICollectionViewDelegate here if needed.
}

Обратите внимание, что этот код не учитывает смещение позиции касания - поэтому ваша клетка будет "прыгать", чтобы центрировать вас под пальцем при начале перетаскивания. Если вы хотите это предотвратить, вам нужно будет определить в свой UIViewController a CGPoint свойство (с именем initialGestureLocationInCell в коде ниже). А затем замените в начальном примере следующим образом:

[...]
        case .began:
        guard let selectedIndexPath = self.uiCollectionView.indexPathForItem(at: gesture.location(in: self.uiCollectionView)) else
        {
            break
        }

        let selectedCell = self.uiCollectionView.cellForItem(at: selectedIndexPath)!
        let gestureLocationInCell_RelativeToOrigin = gesture.location(in: selectedCell)
        let gestureLocationInCell_RelativeToCentre = CGPoint(x: gestureLocationInCell_RelativeToOrigin.x - selectedCell.frame.size.width/2,
                                                             y: gestureLocationInCell_RelativeToOrigin.y - selectedCell.frame.size.height/2)

        self.initialGestureLocationInCell = gestureLocationInCell_RelativeToCentre
        self.uiCollectionView.beginInteractiveMovementForItem(at: selectedIndexPath)

    case .changed:
        let gestureLocationInCollectionView = gesture.location(in: gesture.view!)
        let targetPosition = CGPoint(x: gestureLocationInCollectionView.x - self.initialGestureLocationInCell.x,
                                    y: gestureLocationInCollectionView.y - self.initialGestureLocationInCell.y)

        self.uiCollectionView.updateInteractiveMovementTargetPosition(targetPosition)
[...]

Ответ 3

UICollectionViewController не устанавливает его для распознавания жестов при переупорядочении, когда он встроен в представление контейнера. Неясно, является ли это преднамеренным или ошибкой.

Один обходной путь:

Включите/выключите установку InstallStandardGestureForInteractiveMovement в viewDidAppear (или позже в жизненном цикле) для встроенных UICollectionViewControllers:

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.installsStandardGestureForInteractiveMovement = false
        self.installsStandardGestureForInteractiveMovement = true
    }

Переупорядочение будет работать так же, как не встроенный UICollectionViewController. Источник данных просто нужно объявить

func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)

Обратите внимание, что вам нужно установить false для collectionView clipsToBounds, чтобы иметь возможность видеть ячейки при перетаскивании за пределы представления коллекции. Однако это означает, что ячейки, прокручиваемые за пределами границ, также будут видны, что может оказаться невозможным в зависимости от вашего дизайна.