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

I will try to rebuild completely the Slide class and its xib.

That seems to be a good way, as some of the behaviours you have described cannot be explained...

So I rebuilt a new Slide.swift and Slide.xib.


I noticed that the view is now named Slide Carrier View in the Objects hierarchy.


I deleted Derived data, did a Clean and ran. No change at all !


I'm getting mad on this.


I also noticed that in the ObjC version, the slide carreir shows a subtle gradient of gray. I could not find where it is done. I did not find in code (the only gradient is for the whole collection view background) nor in a nib file. I would like to set a breakpoint on this and see if I go through in Swift…


Otherwise, would there be a way to send the whole project source, or to receive yours (I understand you did the translation).

Could the problem lie here :

- (void)setRepresentedObject:(id)newRepresentedObject {
    [super setRepresentedObject:newRepresentedObject];
    [self.imageFile requestPreviewImage];
}

Which I converted in Swift as

   override var representedObject: AnyObject? {
        get {
            return super.representedObject
        }
        set(newRepresentedObject) {
            super.representedObject = newRepresentedObject
            self.imageFile?.requestPreviewImage() /
        }
    }

With

    func requestPreviewImage() {
        if (self.previewImage == nil) {
            ImageFile.previewLoadingOperationQueue().addOperationWithBlock { () -> Void in
                if self.createImageSource() {
                    let options = [
                        String(kCGImageSourceCreateThumbnailFromImageIfAbsent): true,
                        String(kCGImageSourceThumbnailMaxPixelSize): 160
                    ]
                    let thumbnail = CGImageSourceCreateThumbnailAtIndex(self.imageSource!, 0, options)
                    if (thumbnail != nil) {
                        let image = NSImage(CGImage: thumbnail!, size: NSZeroSize)
                        /
                        NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
                            self.previewImage = image
                        })
                    }
                }
            }
        }
    }


Here is Apple's comment to this func

// We set a Slide's representedObject to point to the ImageFile it stands for. If you aren't using Bindings to provide the desired content for your item's views, an override of -setRepresentedObject: is a handy place to manually set such content when the model object (ImageFile) is first associated with the item (Slide). (Another good place to do that is in the -collectionView:willDisplayItem:forRepresentedObjectAtIndexPath: delegate method, depending how your like to factor your code.)

Our project uses Bindings to populate a Slide's imageView and NSTextFields, but we do use -setRepresentedObject: as an opportunity to request asynchronous loading of the ImageFile's previewImage. When the previewImage has finished loading on a background thread, the ImageFile will get a -setPreviewImage: message, scheduled for delivery on the main thread. The Slide's imageView, whose content is bound to our representedObject's previewImage property, will then automatically show the loaded preview image.

Accepted Answer

to receive yours (I understand you did the translation).

You can find may translated version:

{GitHub's URL}/ooper-shlab/CocoaSlideCollection-Swift

Thank you so much, this one works ! I will look in detail.


I notice you use @objC in several places, I'll do the same.


I notice in my version an error when dragging 2 images backward ; the order of images is not kept.


But once again, thanks so much for all your help and patience all along those days.

Hi,


Thanks again a lot for publishing the code. Incredibly useful. I learn a lot going through it and comparing with my implementation (notably on how to use correctly the optional ?).


I finally found why the frame of the slides didn't show: they are in the SlideArt.xcassets, which I forgot to include !



I now have the "How", but have a lot of questions on the "Why", to really learn from this.


you prefix classes with @objc

When should / need I use @objc ?

Many of your var are declared private. Is this for memory management only or does it have other effect ?


In AAPLBrowserWindowController.swift,

enum is defined as @objc, as well as its instances ;

I did not do so and had a problem to bind a field of a xib to an instance of that enum. Is it required to define enum as @objc to bind ?


You do not call super.windowDidLoad in the windowDidLoad. I thought it was required.

So, when is such a call to super. needed in an override function ?


There is no override init(window: NSWindow!) nor required init?(coder: NSCoder)

If I do so, I have a compiler error when calling super.init() in convenience init.

Why does it work in your code ?


You have many declarations as : private var _layoutKind

what is the purpose of this _layoutKind storage in addition to @objc dynamic var layoutKind ?

When should I use _layoutKind vs layoutKind

Is it needed to implement the getter ?

get {

return _layoutKind

}

In observeValueForKeyPath(keyPath

I had to force the

self.showStatus("\(newSelectedIndexPaths.count) items selected")

as well as

collectionView.animator().insertItemsAtIndexPaths([indexPath])

inside a

dispatch_async(dispatch_get_main_queue(), {}

to avoid compiler warning «This application is modifying the autolayout engine from a background thread »

How to quiet this warning, if not with the dispatch ?


What is the purpose of the second ! after [“indexes“] in

let indexes = change!["indexes"]! as! NSIndexSet


keyPath is an optional. Don’t I need to unwrap to do the comparison ?

if keyPath == imageFilesKey {


I could not find in the documentation (Swift nor Xcode) the syntax for String(format:

Where is it defined ?

In AAPLSlide.swift,

For draggingImageComponents, you write

override var draggingImageComponents: [NSDraggingImageComponent]

I handled it as a func

private func draggingImageComponents() -> NSArray

There seems to be such a method in ObjC, as tells the compiler error without private :

Method 'draggingImageComponents()' with Objective-C selector 'draggingImageComponents' conflicts with getter for 'draggingImageComponents' from superclass 'NSCollectionViewItem' with the same Objective-C selector

But, after putting again the private, I kept getting the error message until I deleted Derived data ?!


I will probably have a few more, but it's enouygh for this post.

First of all, please remember my style of coding may not be the best recommended and you can find many points to improve.

you prefix classes with @objc

When should / need I use @objc ?


The classes in Apple's sample codes may be instantiated through nib, so when translating, it's safe to have an Objective-C compatible name included in .xib/.storyboard. As you see, manipulating existing nib files is not an easy thing. Not all @objc annotations are necessary. I rarely use @objc annotations in my original app.


Simply saying, it makes translating easier.


Many of your var are declared private. Is this for memory management only or does it have other effect ?


My main intension to make things `private` is giving a better opportunity of optimizing to compiler. Swift has only properties, no explicit ivars, but `private` properties can easily optimized and Swift generates the best optimized code which has no loss accessing ivars through setters/getters.


enum is defined as @objc, as well as its instances ;

I did not do so and had a problem to bind a field of a xib to an instance of that enum. Is it required to define enum as @objc to bind ?


Yes. Pure Swift enums are one listed in the "Objective-C incompatible" things. With putting @objc, the enum type can interact with Objective-C part, as well as imported enums work both in Swift and Objective-C.


You do not call super.windowDidLoad in the windowDidLoad. I thought it was required.

So, when is such a call to super. needed in an override function ?


Not all lifecycle events require calling super. Some super calls are clearly stated as "do nothing", or you may need to change the default behaivour of that method. But generally it's safe to put one. In this case, the original sample code does not have one and seems working without it, I just followed the original author's decision.


There is no override init(window: NSWindow!) nor required init?(coder: NSCoder)

If I do so, I have a compiler error when calling super.init() in convenience init.

Why does it work in your code ?


As you know, Swift has strict rules in initializers so this is one part I have suffered translating. The original code calls [super initWithWindowNibName:@"BrowserWindow"] inside its initializer, it's mandatory. But the initializer is marked as convenience in the generated Swift header of NSWindowController. We cannot define designated initializers with calling super's convenience initializer inside it, in Swift.

Thus:

- We need to declare the init(rootURL:) as a convenience initializer

- So, we need init(windonwNibName:) as self's, not super's.

- In Swift, without declaring designate initilizers, all designated initializers are inherited.

- And with overriding or inheriting all designated initializers, all convenience initializers are inherited.

The last two above means that if we do not declare designated initializers, all initializers (both designated and convenience) are inherited.

Done. All initializers inherited and we can use self.init(windonwNibName:) in our convenience init(rootURL:) .


You have many declarations as : private var _layoutKind

what is the purpose of this _layoutKind storage in addition to @objc dynamic var layoutKind ?

The private stored property:

private var _layoutKind: SlideLayoutKind = .Circular

Corresponds to the Objective-C ivar declaration in .h:

SlideLayoutKind layoutKind;


And the computed property:

    @objc dynamic var layoutKind: SlideLayoutKind {
        get {
            return _layoutKind
        }
     
        set(newLayoutKind) {
            if _layoutKind != newLayoutKind {
                if newLayoutKind != .Wrapped && _groupByTag {
                    NSAnimationContext.currentContext().duration = 0.0 /
                    groupByTag = false
                }
                _layoutKind = newLayoutKind
                self.updateLayout()
            }
        }
    }

Corresponds to the getter and setter method in .m:

- (SlideLayoutKind)layoutKind {
    return layoutKind;
}
- (void)setLayoutKind:(SlideLayoutKind)newLayoutKind {
    if (layoutKind != newLayoutKind) {
        if (newLayoutKind != SlideLayoutKindWrapped && groupByTag) {
            [[NSAnimationContext currentContext] setDuration:0.0]; /
            [self setGroupByTag:NO];
        }
        layoutKind = newLayoutKind;
        [self updateLayout];
    }
}


When should I use _layoutKind vs layoutKind

Is it needed to implement the getter ?

You have learnt how to use willSet/didSet, but in this case, the Objective-C setter does something before and after updating ivar, so you may need both willSet and didSet, which makes your code complex and hard to maintain. So, I took sort of "direct translation" strategy in this case.

And Yes, you need to implement getter in this strategy.


Getting too long, later next parts.

I had to force the

...

How to quiet this warning, if not with the dispatch ?


Not sure about this part, thread usage written in other parts may differ. Just that I couldn't observe such warings...


What is the purpose of the second ! after [“indexes“] in

let indexes = change!["indexes"]! as! NSIndexSet


As you see, it is sort of redundant. I intend to express that the entry "indexes" needs to exist in the dictionary.


keyPath is an optional. Don’t I need to unwrap to do the comparison ?

if keyPath == imageFilesKey {


Equality operator `==` is overloaded for two Optionals. If one side is non-Optional, it is treated as an Optional, as well as you can pass non-Optional values to functions which take Optional arguments.


I could not find in the documentation (Swift nor Xcode) the syntax for String(format:

Where is it defined ?


Indeed, it is not well documented. Add `import Foundation`, and Command-click on "Foundation". You can find most NSString methods are imported as String method in Swift.


For draggingImageComponents, you write

...

But, after putting again the private, I kept getting the error message until I deleted Derived data ?!


`draggingImageComponents` is declared as a property in NSCollectionView.h, so you need to override it as property. Objective-C's method with no aruguments can be a property getter or a method with no arguments in Swift. In this case it needs to be a property. With putting `private`, you may succeed to silence the compiler, but Swift compiler does not expose Objective-C compatible entry for private methods, so the behaviour of the class may be inconsistent.


And I have forgotten to write this in the last post.

I finally found why the frame of the slides didn't show: they are in the SlideArt.xcassets, which I forgot to include !

Thanks for finding this out and sharing your experience. I will remember that this sort of things may cause the behaviour you have described.

So, that will close this long discussion. Thanks for your help and patience. I will now analyze carefully your answers, for deep understanding.


It will help me progress a lot in my Swift and Cocoa education, till I cann call me OOfils (Pere means father, fils means son 😁.


Claude


PS: for There is no override init(window: NSWindow!) nor required init?(coder: NSCoder)

I have deleted the two initializers, initialized properties that needed to, even added @objc for the class, but I keep getting

BrowserWindowController.swift:43:7: Class 'BrowserWindowController' has no initializers


But I will live with this. Meet you in a next thread …

I have found why the previewImages didn't show unless window was scrolled: I forget to declare previewImage as dynamic !


PS : in your code you decalre

    @NSCopying var url: NSURL
    @objc dynamic var fileType: String?
    var fileSize: UInt64 = 0
    @NSCopying var dateLastUpdated: NSDate?


why is @NSCopying needed ? What's its effect ?

Detecting all KVO targets is not an easy thing, I may be missing some...


why is @NSCopying needed ? What's its effect ?

It is a direct translation of this sort of property declaration in Objective-C:

@property(copy) NSDate *dateLastUpdated;

On @NSCopying property, generated setters become something like:

set {
    `_$dateLastUpdated` = newValue.copy() as! NSDate
}


As you know, all Objective-C classes are reference types, so if the assigned instance is shared and mutable, it may cause inconsistensy. To avoid that, `copy` is giving value semantics to properties with always-copy strategy.

In fact, most of them may not be needed, as they will not be shared or changed after assignment.

Hi, If you are interested for your books, I noticed an error in the wrapped mode when moving images around. When moved backward, images were not repositioned properly.


I suspect that's the purpose of enumerateDraggingItemsWithOptions in the objc, with the .Reverse option. But that does not seem to be available in Swift.


I made the following change in

func collectionView(collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: NSIndexPath, dropOperation: NSCollectionViewDropOperation) -> Bool


It may not be very clean but as far as I tested it works, for moving a single image or multiple images.


        if (self.indexPathsOfItemsBeingDragged != []) {
           // Yes, existing items are being dragged within our imageCollectionView.
            if (self.groupByTag) {
                 result = false
            } else {
                // Walk forward through fromItemIndex values > toItemIndex, to keep our "from" and "to" indexes valid as we go, moving items one at a time.
                var toItemIndex = indexPath.item
                var adjustedIndexPathsOfItemsBeingDragged: Set<NSIndexPath> = []    // To copy indexPath, at their modified position
                var adjustedToItemIndex = indexPath.item - 1
         
                for fromIndexPath in indexPathsOfItemsBeingDragged {
                    let fromItemIndex = fromIndexPath.item
                    if fromItemIndex > toItemIndex {
                       // For each step: First, modify our model.
                        imageCollection?.moveImageFileFromIndex(fromItemIndex, toIndex: toItemIndex)
                        let toIndexPath = NSIndexPath(forItem: toItemIndex, inSection:0)
                        adjustedIndexPathsOfItemsBeingDragged.insert(toIndexPath)   // the IndexSet with the modified positions
                        imageCollectionView.animator().moveItemAtIndexPath(NSIndexPath(forItem: fromItemIndex, inSection: indexPath.section), toIndexPath: NSIndexPath(forItem: toItemIndex, inSection: indexPath.section))
                         ++toItemIndex
                        adjustedToItemIndex = indexPath.item // In this case, adjustedToItemIndex must not be -1
                    } else {
                        adjustedIndexPathsOfItemsBeingDragged = indexPathsOfItemsBeingDragged
                    }
                }
            /
          // Walk backward through fromItemIndex values < toItemIndex, to keep our "from" and "to" indexes valid as we go, moving items one at a time.
            // BUG was there when moving image to the left
                for fromIndexPath in adjustedIndexPathsOfItemsBeingDragged { /
                    let fromItemIndex = fromIndexPath.item
                    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;
            }       // else groupByTag

Thanks for pointing it out. I fixed it in a little bit different manner, so please check it when you can make some time to.

It does not work if you move a group of icons (just btest with 2) to the left. The second image does not move to the left.

This is very surprising, because looks like you have included the original objc .h and .m ; and it works in the full objc version !?!

Thanks super much for checking.


This is very surprising, because looks like you have included the original objc .h and .m ; and it works in the full objc version !?!

The two files are included in the project to view two files side by side, not included in the target, so they are not compiled. Try using Assistant Editor in this setting if not yet.

But as for now it is I who need to use this feature...

NSCollectionViewDataSource method return different result in Objc and Swift ?
 
 
Q