NSCollectionViewDataSource method return different result in Objc and Swift ?

I have ported to Swift an existing ObjC code.


I have NSCollectionViewDataSource Methods which should be exactly doing the same :


In ObjC

- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
    // Message back to the collectionView, asking it to make a @"Slide" item associated with the given item indexPath.  The collectionView will first check whether an NSNib or item Class has been registered with that name (via -registerNib:forItemWithIdentifier: or -registerClass:forItemWithIdentifier:).  Failing that, the collectionView will search for a .nib file named "Slide".  Since our .nib file is named "Slide.nib", no registration is necessary.
    NSCollectionViewItem *item = [collectionView makeItemWithIdentifier:@"Slide" forIndexPath:indexPath];
    AAPLImageFile *imageFile = [self imageFileAtIndexPath:indexPath];
    item.representedObject = imageFile;
    return item;
}


In Swift

    func collectionView(collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath) -> NSCollectionViewItem {
        //
        let item = collectionView.makeItemWithIdentifier("Slide", forIndexPath: indexPath)
        let imageFile = self.imageFileAtIndexPath(indexPath)
        item.representedObject = imageFile;
        return item;
    }


I set a breakpoint at the return line. In ObjC, the slide.nib is displayed in the view, not in Swift.

I have checked all the nib files to make sure they are exactly identical in Swift and ObjC (notably the connections which define dataSource and delegates)

I do wonder why such a different behavior (in fact, nib never displayed in Swift)


item object appear different in ObjC and Swift. I wonder if it's just a different way to present in ObjC and Swift or a real problem.


ObjC (partial content)


item AAPLSlide * 0x618000120fa0

NSCollectionViewItem

NSViewController

NSResponder

_nibName id 0x0

_nibBundle NSBundle * @"/Users/me/Library/Developer/Xcode/DerivedData/CocoaSlideCollection ewnzbbfwoqqfsjgavhdwzvwhctyx/Build/Products/Debug/CocoaSlideCollection.app" 0x0000600000080af0

_representedObject AAPLImageFile * 0x61800008a230

_title id 0x0

view AAPLSlideCarrierView * 0x618000140f20

// ......

_collectionView NSCollectionView * 0x10050de20

NSView

_content id 0x0

_selectionIndexes NSMutableIndexSet * 0 indexes

_animationDuration double 0.25


_delegate AAPLBrowserWindowController * 0x6100000e0d00 n// in collectionView structure


Swift (partial content)


item NSCollectionViewItem 0x0000600000122300

_TtC20CocoaSlideCollection5Slide CocoaSlideCollection.Slide // aka bridge type ????

NSCollectionViewItem

NSViewController

NSResponder

_nibName NSIndexPath * 0x2000169

_nibBundle NSCollectionView * 0x10080fe80

NSView

_content id 0x0

// ....

_animationDuration double 0.25

_delegate CocoaSlideCollection.BrowserWindowController * 0x608000100120 // in nibBundle, which is also NSCollectionView

NSWindowController

NSResponder

_window NSWindow * 0x6100001e0200

OK, got it. I saw the reverse (I didn't know of lazy.reverse() type of construction !)


I tried a simple change in adjustedToItemIndex (as per my own implementation), doesn't work either.


The problem seems to lie in part in indexPathsOfItemsBeingDragged. The items inside are no more pointing to the right ones after the first loop for fromIndexPath in indexPathsOfItemsBeingDragged.


PS: where are you based ? I'm in south of France.

Tested again my code. Does not seem to work ! I'm sure it was yesterday ?!

One sure thing is that the original Objective-C code runs perfectly well in this part. I believe we can find a steady solution, but it seems to take a little more time.


PS. I'm in Osaka (as shown in my profile). Visited Marseille last year. Near Marseille?

The problem seems to lie in part in indexPathsOfItemsBeingDragged. The items inside are no more pointing to the right ones after the first loop for fromIndexPath in indexPathsOfItemsBeingDragged.

I believe I have found the cause of the bug.

I misunderstood the method `enumerateIndexPathsWithOptions:usingBlock:` as a simple iterating method like `enumerateObjectsWithOptions:usingBlock:`.

And `indexPathsOfItemsBeingDragged` is a Set (NSSet), so its simple iteration retrieves elements in an unpredictable order, which is not the same as `enumerateIndexPathsWithOptions:usingBlock:`.

Executes the given block for each NSIndexPath in the set. The index paths are enumerated in the order defined by NSIndexPath's -compare: method. For CollectionView item index paths, this means all index paths in section 0, in ascending order, followed by all index paths in section 1, and so on. You may pass the NSEnumerationReverse option to enumerate in the reverse order. Set *stop = YES if desired, to halt the enumeration early.


Thus, we need sorting indexPathsOfItemsBeingDragged (by NSIndexPath's compare: method) before iterating, iterate in normal order, then iterate in reverse order.

I think I have found the issue.

In obj, the items are read in a given order in NSSet<NSIndexPath *> *indexPathsOfItemsBeingDragged

In swift, set is unordered, so it is not read in a predefined order. So, the reverse is probably ignored.

Is there a way to order a set ? Or should we build an array from the set and order it ?


Osaka, great. I've been in Japan a few times and enjoyed. (Tokyo last year).

Toulouse is 400 kms west of Marseille, along Pyrenees mountains.

Yes, I implemented it a bit differently (I do not see how to use the compare method) with a orderedArrayIndexPathsOfItemsBeingDragged and defining the sorting order approprietaly.

Now it works.


        if (self.indexPathsOfItemsBeingDragged != []) {
            if (self.groupByTag) {
                result = false
             } else {
                var toItemIndex = indexPath.item    /
                var orderedArrayIndexPathsOfItemsBeingDragged : Array<NSIndexPath> = []  
                orderedArrayIndexPathsOfItemsBeingDragged.appendContentsOf(indexPathsOfItemsBeingDragged)
                orderedArrayIndexPathsOfItemsBeingDragged.sortInPlace({i1, i2 in return (i1.item < i2.item)})    // ascending order
                var adjustedToItemIndex = indexPath.item - 1
                 for fromIndexPath in orderedArrayIndexPathsOfItemsBeingDragged {
                    let fromItemIndex = fromIndexPath.item
                    if fromItemIndex > toItemIndex {
                          imageCollection?.moveImageFileFromIndex(fromItemIndex, toIndex: toItemIndex)
                        imageCollectionView.animator().moveItemAtIndexPath(NSIndexPath(forItem: fromItemIndex, inSection: indexPath.section), toIndexPath: NSIndexPath(forItem: toItemIndex, inSection: indexPath.section))
                       ++toItemIndex
                    } 
                }


            // Walk backward through fromItemIndex values < toItemIndex, to keep our "from" and "to" indexes valid as we go, moving items one at a time.                          
               orderedArrayIndexPathsOfItemsBeingDragged.sortInPlace({i1, i2 in return (i1.item > i2.item)})  // reverse order

               for fromIndexPath in orderedArrayIndexPathsOfItemsBeingDragged {
                    let fromItemIndex = fromIndexPath.item
                    if (fromItemIndex < adjustedToItemIndex) {
                      imageCollection?.moveImageFileFromIndex(fromItemIndex, toIndex: adjustedToItemIndex)
                    let adjustedToIndexPath = NSIndexPath(forItem: adjustedToItemIndex, inSection: indexPath.section)
                    imageCollectionView.animator().moveItemAtIndexPath(NSIndexPath(forItem: fromItemIndex, inSection: indexPath.section), toIndexPath: adjustedToIndexPath)
                    --adjustedToItemIndex
                    }
                }
            /
            result = true;
            } 

As you know your comparison closure used in `sortInPlace` works when `row`s of all NSIndexPaths are the same, as in the sample code.


And using `lazy.reverse()` is a little more efficient than sorting a sorted array, worth remembering how and when to use it.

It works with enumerateIndexPathsWithOptions ! Very clean.


Just regret it is not yet in the documentation of NSCollection !

NSCollectionViewDataSource method return different result in Objc and Swift ?
 
 
Q