1
votes

To move items in a UICollectionView I use a UILongPressGestureRecognizer.

I implemented the following gesture handler:

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()
    }
}

Together with a correct override of the collection view delegate method moveItemAtIndexPath, moving cells within a section works perfect. Furthermore, moving cells to other visible sections also works as expected. However, problems occur when moving cells to sections that become visible by panning/scrolling down, resulting in the following error (I use a Realm database):

'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0.  The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (0), plus or minus the number of items inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'

The problem seems related to the tracking of location changes by the gesture recognizer in updateInteractiveMovementTargetPosition since the error occurs in this method.

I suppose that updateInteractiveMovementTargetPosition directly calls the collection view method moveItemAtIndexPath (not via the delegate) which results in an error since intermediate cell location changes are not updated in the Realm database. Or am I incorrect?

Would anybody know why moving between sections works for visible sections, but not during panning? And how should the scrolling/panning be handled correctly?

1

1 Answers

0
votes

UICollectionView & UITableView APIs are very unforgiving when it comes to making updates in the UI state and requiring those to be mirrored in the data source.

You need to update the Realm to reflect the UI state in order to keep UICollectionView happy.

Here's an example of a Realm-backed UITableViewController with reorderable cells that you could adapt for your UICollectionView use case:

import UIKit
import RealmSwift

class ObjectList: Object {
    let list = List<DemoObject>()
}

class DemoObject: Object {
    dynamic var title = ""
}

class TableViewController: UITableViewController {
    var items: List<DemoObject>?

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.editing = true

        let realm = try! Realm()
        if realm.isEmpty {
            try! realm.write {
                let list = ObjectList()
                list.list.appendContentsOf(["one", "two", "three"].map {
                    DemoObject(value: [$0])
                })
                realm.add(list)
            }
        }
        items = realm.objects(ObjectList.self).first!.list
    }

    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        return items?.count ?? 0
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
        cell.textLabel?.text = items?[indexPath.row].title
        return cell
    }

    override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
        return .None
    }

    override func tableView(tableView: UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        return false
    }

    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
        guard let items = items else { return }
        try! items.realm?.write {
            let itemToMove = items[fromIndexPath.row]
            items.removeAtIndex(fromIndexPath.row)
            items.insert(itemToMove, atIndex: toIndexPath.row)
        }
    }
}