Reputation: 4797
I'm using an NSCollectionView
to display various objects. The whole things works rather well, except for one annoying thing. I cannot figure out how to access the various controls on the view used to represent each object in the collection.
Here's the setup:
NSCollectionView
into my view in IB.NSCollectionViewItem
. Mapped my class in IB.NSBox
to act as the view for each object in the collection. Also mapped this class in IB and connected it to the view
property of my NSCollectionViewItem
subclass.The view:
The resulting collection view:
Reasoning that that my subclass of NSCollectionViewItem
is basically a controller for each view in the collection, I made referencing outlets of the various controls in the view in my controller subclass:
@interface SourceCollectionViewItem : NSCollectionViewItem
@property (weak) IBOutlet NSTextField *nameTextField;
@property (weak) IBOutlet NSTextField *typeTextField;
@property (weak) IBOutlet RSLabelView *labelView;
@property (weak) IBOutlet NSButton *viewButton;
@end
When I inspect any instance of SourceCollectionViewItem
in the debugger, all the properties show up as nil despite the fact that I can actually see them on my screen and that everything is displayed as it should be.
My setup was inspired by Apple's sample app IconCollection.
I am obviously missing something. What?
EDIT: I found various posts hinting at a similar issue: CocoaBuilder.com and this question on SO.
EDIT: Just to be complete: this post deals with the subject as well and delivers a solution based on a combination of the options mentioned in the accepted answer.
Upvotes: 3
Views: 2128
Reputation: 9085
Great question. Like @hamstergene suggests, you can use copyWithZone
, it will be much more efficient compared to newItemForRepresentedObject
. However viewWithTag
is not always an option, first, because not everything can be tagged (easily), and, second, using tag for this purpose is a little wrong. Here's a cool approach with performance in mind, in Swift.
import AppKit
class MyViewController: NSCollectionItemView
{
// Here you are cloning the original item loaded from the storyboard, which has
// outlets available, but as you've seen the default implementation doesn't take
// care of them. Each view has a unique identifiers, which you can use to find it
// in sublayers. What's really cool about this, is that you don't need to assign
// any tags or do anything else while having advantage of better performance using
// cached nib object.
override func copyWithZone(zone: NSZone) -> AnyObject {
let copy: NSCollectionItemView = super.copyWithZone(zone) as! NSCollectionItemView
let oldView: RecordingView = self.view as! MyView
let newView: RecordingView = copy.view as! MyView
newView.foo = newView.viewWithIdentifier(oldView.foo.identifier!) as! NSTextfield
newView.bar = newView.viewWithIdentifier(oldView.bar.identifier!) as! NSImageView
return copy
}
}
@IBDesignable class MyView: View
{
// Custom collection view item view. Lets assume inside of it you have two subviews which you want
// to access in your code.
@IBOutlet weak var foo: NSTextfield!
@IBOutlet weak var bar: NSImageView!
}
extension NSView
{
// Similar to viewWithTag, finds views with the given identifier.
func viewWithIdentifier(identifier: String) -> NSView? {
for subview in self.subviews {
if subview.identifier == identifier {
return subview
} else if subview.subviews.count > 0, let subview: NSView = subview.viewWithIdentifier(identifier) {
return subview
}
}
return nil
}
}
Upvotes: 0
Reputation: 105
I found that overriding NSCollectionViewItem's -setRepresentedObject:
could also be a good choice, as it is called on the new Item when all IBOutlet seem to be ready. After the call to super
you can do whatever is needed:
- (void)setRepresentedObject:(id)representedObject
{
if (representedObject) {
[super setRepresentedObject:representedObject];
[self.anOutlet bind:@"property" toObject:self.representedObject withKeyPath:@"representeProperty" options:nil];
}
}
I used this method to bind a custom property of an interface object. The check is there to avoid useless calls, when the representedObject is not yet ready. The project uses a separate xib for the ViewItem, as explained in the links in the original edits.
Upvotes: 1
Reputation: 24439
Outlets are set during nib loading, and only the prototype item is loaded from nib and has its outlets assigned. All other ViewItem
s and their View
s are cloned from the prototype, in that case outlets are just instance variables that are never initialized.
Here are the options I could come up with:
newItemForRepresentedObject:
of collection view and reload nib instead of cloning the prototype. But this will probably hurt the performance greatly.copyWithZone
of collection view item and assign outlets manually using viewWithTag:
to find them.Upvotes: 6