NSCollectionViewItem with a custom view - macos

I've been struggling with trying to create an NSCollectionView that has a set of NSCollectionViewItems with a custom view. The code works fine when the controls on the item view are standard AppKit controls, but once I add a custom NSView, there's no way to bind it from Interface Builder.
From spending some hours searching the internet, there appears to be a lot of options to solve this but all seem specialised. Is there some simple example code that demonstrates how, given a CustomImage * on the item view, to set the image property on that custom view?
The model that provides data for each item is:
#interface MyItem : NSObject
#property (retain, readwrite) NSImage * image;
#property (retain, readwrite) NSString * name;
#end
The NSCollectionViewItem subclass is:
#interface MyCollectionViewItem : NSCollectionViewItem
// Properties
#property (strong) IBOutlet NSTextField * name;
#property (strong) IBOutlet CustomImage * image;
#end
where CustomImage is simply a subclass of NSImageView.
I tried subclassing NSCollectionView and overriding newItemForRepresentedObject as some answers suggested and assigning there:
MyItem * item = (MyItem *)object;
MyCollectionViewItem * newItem = (MyCollectionViewItem *)[super newItemForRepresentedObject:object];
NSView *view = [newItem view];
[view bind:#"name" toObject:item withKeyPath:#"name" options:nil];
[view bind:#"image" toObject:item withKeyPath:#"image" options:nil];
return newItem;
but this just blew up in the bind call with an error that 'name' doesn't exist.
This should, in theory, be an extremely simple thing to solve but none of the answers I've found make this clear. An alternative would be to ditch NSCollectionView and use one of the simpler replacements on GitHub but I'd like to have a last attempt to see if this is solvable first.
Thanks!

How did you add the CustomImage instance to the item view?
If you drag a "Custom View" in and then change the class, IB doesn't treat it like an NSImageView.
However, if you drag out an NSImageView and then change the class, IB should still treat it like an NSImageView and you should be able to bind its bindings like normal. In that case, you can bind its Value binding to the collection view item, model key path "representedObject.image".

Related

Set view as delegate to viewcontroller

I'm having problem passing data and executing functions in the viewcontroller from the view. I want to access label outlets in the viewcontroller from the view (yeah I know it might be bad structure of my app).
Got delegations working on UIPopovers but how can I set the delegate of the view to viewcontroller?
For example, you have a subclass of UIView. Let's name it SubClassUIView;
In another view, you want to use the data, from SubClassUIView.
So, your SubClassUIView should be done like this:
SubClassUIView.h
#interface SubClassUIView:UIView
#property (nonatomic, retain) UILabel* someLabelYouWantToUse;
#end.
SubClassUIView.m
#implementation SubClassUIView
#synthesize someLabelYouWantToUse;
#end.
And to access someLabelYouWantToUse
SubClassUIView* scView = [SubClassUIView alloc]init];
NSLog(#"%#", scView.someLabelYouWantToUse.text);

How to prevent retain cycles caused by binding to self

I have an application where I need to access model data from my subviews. I've been using bindings to pass data across views; however, the bindings to self seem to be causing retain cycles (dealloc never gets called). When should I remove the bindings if not in the dealloc method? Thanks.
P.S. I know the method of binding to a proxy object controller, but I'd like to avoid using it if possible.
Here's an example of what I've been doing:
// Top-level Project view
#interface ProjectViewController : NSViewController {
FoldersView *foldersView;
}
#property (strong) NSObjectController *projectObjectController; // holds Project instance
end
// Displays folders
#interface FoldersView : NSView {
FolderView *folderView;
}
#property (weak) NSObjectController *projectObjectController; // binded from parent
#property (strong) NSArrayController *foldersArrayController; // binded to project.folders
#end
// Displays selected folder
#interface FolderView : NSView
#property (weak) NSArrayController *foldersArrayController; // binded from parent
#property (strong) NSObjectController *folderObjectController; // binded to folders.selection
#end
The bindings are the preferred way of removing C part (boilerplate code) from the MVC trinity. So your approach to handling this problem is correct.

displaying an image in cocoa

I am totally new at this, but here goes:
I want to press a button in my application, and have an image display (I am planning to read it off of a camera, but to start with, I will open a .TIF file.) However, within the interface builder, I can make buttons within an NSObject object, but the literature makes it sound like I need to make an NSView object to display a file. The problem is, when I do this, the NSObject object does not seem to talk to the NSView object. I am trying to do something like:
NSString *inFilePath;
inFilePath = #"/Volumes/Data/Temp/Smiles.tiff";
NSImage *TestImage = [[NSImage alloc] initWithContentsOfFile:inFilePath];
[MyView setImage:TestImage];
Here, MyView is the NSView object. I get warnings that MyView may not respond to setImage. I have tried to define an IBOutlet within the NSObject object, and although I can connect it within the interface builder, console gives me the error:
unrecognized selector sent to class 0x1e080
So, it's not clear what the next step is. Is there an easy way to get two different objects to "talk to" each other?
Thanks
You want an NSImageView object. In the Interface Builder library this is called an Image Well, but you can configure it so that it doesn't have a bezel.
NSImageView is a subclass of NSView that is optimised for displaying images.
In your header (.h) file, you should have something like this:
#interface MyController : NSObject
{
//this declares an outlet so you can hook up the
//image view in Interface Builder
IBOutlet NSImageView* imageView;
}
#end
And in your implementation (.m) file:
#implementation MyController
//called once the nib is loaded and all outlets are available
- (void)awakeFromNib
{
NSString *inFilePath = #"/Volumes/Data/Temp/Smiles.tiff";
NSImage *testImage = [[NSImage alloc] initWithContentsOfFile:inFilePath];
#if !__has_feature(objc_arc)
[testImage autorelease];
#endif
//imageView is your outlet
[imageView setImage:testImage];
}
#end
In Interface Builder you should hook up the imageView outlet of your class to point to the NSImageView you placed on your view.

Getting around IBActions limited scope

I have an NSCollectionView and the view is an NSBox with a label and an NSButton. I want a double click or a click of the NSButton to tell the controller to perform an action with the represented object of the NSCollectionViewItem. The Item View is has been subclassed, the code is as follows:
#import <Cocoa/Cocoa.h>
#import "WizardItem.h"
#interface WizardItemView : NSBox {
id delegate;
IBOutlet NSCollectionViewItem * viewItem;
WizardItem * wizardItem;
}
#property(readwrite,retain) WizardItem * wizardItem;
#property(readwrite,retain) id delegate;
-(IBAction)start:(id)sender;
#end
#import "WizardItemView.h"
#implementation WizardItemView
#synthesize wizardItem, delegate;
-(void)awakeFromNib {
[self bind:#"wizardItem" toObject:viewItem withKeyPath:#"representedObject" options:nil];
}
-(void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
if([event clickCount] > 1) {
[delegate performAction:[wizardItem action]];
}
}
-(IBAction)start:(id)sender {
[delegate performAction:[wizardItem action]];
}
#end
The problem I've run into is that as an IBAction, the only things in the scope of -start are the things that have been bound in IB, so delegate and viewItem. This means that I cannot get at the represented object to send it to the delegate.
Is there a way around this limited scope or a better way or getting hold of the represented object?
Thanks.
Firstly, you almost never need to subclass views.
Bind doesn't do what you think - you want addObserver:forKeyPath:options:context: (You should try to understand what -bind is for tho ).
When you say "the key seems to be it being the "prototype" view for an NSCollectionViewItem" I think you are really confused…
Forget IBOutlet & IBAction - they don't mean anything if you are not Interface Builder. "Prototype" means nothing in Objective-c.
The two methods in the view do not have different scope in any way - there is no difference between them at all. They are both methods, equivalent in every way apart from their names (and of course the code they contain).
If wizardItem is null in -start but has a value in -mouseDown this is wholly to do with the timing that they are called. You either have an object that is going away too soon or isn't yet created at a point you think it is.
Are you familiar with NSZombie? You will find it very useful.

Cocoa Bindings: NSObjectController not KVC-compliant for the representedObject property

I've been through a bunch of Core Data examples and the Apple documentation. I'm at a wall after working on this all day.
All I want to happen is I type some text into a text field, save the file, open it again and see the text there.
I made a very very simple Core Data document-based app to experiment. Here are the particulars:
1) The data model has one Entity ("Note") with one attribute ("title") which is an NSString.
2) I created a view controller "ManagingViewController" that loads in a view called "NoteView" into a box in MyDocument.xib without a problem. NoteView.nib has just one NSTextField in it.
ManagingViewController.h
#import <Cocoa/Cocoa.h>
#import "Note.h"
#interface ManagingViewController : NSViewController {
NSManagedObjectContext *managedObjectContext;
IBOutlet NSTextField *title;
}
#property (retain) NSManagedObjectContext *managedObjectContext;
#property (retain, readwrite) NSTextField *title;
#end
and ManagingViewController.m
#import "ManagingViewController.h"
#import "Note.h"
#implementation ManagingViewController
#synthesize managedObjectContext;
#synthesize title;
- (id)init
{
if (![super initWithNibName:#"NoteView" bundle:nil]) {
return nil;
}
return self;
}
#end
I have a NSManagedObject called "Note.h"
#import <CoreData/CoreData.h>
#import "ManagingViewController.h"
#interface Note : NSManagedObject
{
}
#property (nonatomic, retain) NSString * title;
#end
and the .m file:
#import "Note.h"
#import "ManagingViewController.h"
#implementation Note
#dynamic title;
#end
In NoteView.nib my:
1) File's Owner is ManagingViewController and the IBOutlets to the Text Field and the view are connected.
2) I dragged over an NSObjectController object into the Interface Builder document window called "Note Object Controller". I set mode to "Entity" and the Entity Name to "Note". "Prepares content" and "Editable" are checked on. (All the examples I've done and been able to find use an NSArrayController here. I don't need an array controller right? I do want to be able to open multiple windows for the same app but I still don't think I need an arraycontroller? All the examples have a NSTableView and a add button. There's no need for an add button here since I don't have an NSTableView).
3) The NSTextView bindings for value I have it bound to "Note Object Controller" with a controller key of representedObject and a Model Key Path of title.
When I run my app I get
[<NSObjectController 0x20004c200> addObserver:<NSTextValueBinder 0x20009eee0>
forKeyPath:#"representedObject.title" options:0x0 context:0x20009f380] was
sent to an object that is not KVC-compliant for the "representedObject" property.
What am I doing wrong? I want to type in the text field, save the file, open it again and see the text there.
[<NSObjectController 0x20004c200> addObserver:<NSTextValueBinder 0x20009eee0> forKeyPath:#"representedObject.title" options:0x0 context:0x20009f380] was sent to an object that is not KVC-compliant for the "representedObject" property.
What am I doing wrong?
The error message tells you what you're doing wrong: You're trying to bind to the representedObject property of your object controller, but it doesn't have one. Binding to properties that don't exist cannot work.
The Note is the content object of the NSObjectController, so that's the controller key you need to bind to: content.

Resources