Why am I getting an error about NSCollectionView not being nil? - objective-c

This is my BookListObject.h:
# property (nonatomic,retain)NSImage *bookImage;
# property (nonatomic,copy) NSString *bookName;
and I connect these bindings:
bookImage ----- representedObject.bookImage
bookName ------representedObject.bookName
arrayController-------arrangedObject
-(void)awakeFromNib{
BookListObject * book=[[BookListObject alloc] init];
book.bookName=#"unknown";
book.bookImage=[NSImage imageNamed:#"dis"];
_bookList=[NSMutableArray arrayWithObjects:book, nil];
[arrayController addObject:_bookList];
}
I have seen this video.
When I run my project, it crashes and prompts that NSCollectionView item prototype must not be nil.
I found the following method, but it doesn't work:
NSCollectionViewItem *itemPrototype = [self.storyboard instantiateControllerWithIdentifier:#"collectionViewItem"];
self.bookListCV.itemPrototype=itemPrototype;
The problem remains. What should I do? How can I bind the collectionView with collectionViewItem in the storyboard? I think XCode6 supports it only when you use xib.

Related

Objective C programmatical binding doesn't work

An imageView is properly bound as IBOutlet ("imageOKData"). I have programmatically bound its visibility (hidden) to a BOOL property of a custom object that already is available when creating the binding.
in ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Create binding: %hhd",_global.dataFile.imported);
// --> "Create binding: 0"
NSDictionary* reverseBool = [NSDictionary dictionaryWithObjectsAndKeys:NSValueTransformerNameBindingOption,NSNegateBooleanTransformerName,
nil];
[_imageOKData bind:#"hidden" toObject:_global.dataFile withKeyPath:#"imported" options:reverseBool];
}
With the above code the image should be hidden once the viewDidLoad. But its still visible. Even if i remove the reverseBool options it' still there. What am i doing wrong?
It was the options dictionary. Jeeeez... i've mistaken the order. It must be
NSDictionary* reverseBool = [NSDictionary dictionaryWithObjectsAndKeys:NSNegateBooleanTransformerName, NSValueTransformerNameBindingOption,nil];
Sometimes you don't see the hand in front of your face.

NSImage memory leak

First of all: I already searched on google and SO for solutions - none worked.
I've got an application which loads the artwork of the current iTunes track and displays it; this is stored in a NSImage instance, among some other variables, in a class:
#interface infoBundle : NSObject
#property (strong) NSImage *track_artwork;
#property (weak) NSString *track_title;
#property (weak) NSString *track_album;
#property (weak) NSString *track_artist;
#end
Then, an instance of this class is created:
-(infoBundle*)returnInfoBundle {
infoBundle* tmpBundle = [[infoBundle alloc]init];
tmpBundle.track_artwork = [[NSImage alloc]initWithData:[(iTunesArtwork *)[[[iTunes currentTrack] artworks] objectAtIndex:0] rawData]];
[...]
return tmpBundle;
}
And later used:
-(void)iTunesDidChange {
infoBundle* tmpBundle = [self returnInfoBundle];
[...]
[imageView setImage:tmpBundle.track_artwork];
}
That's eating up ~2MB (Cover size, I'd guess) per call of iTunesDidChange.
I already tried:
[tmpBundle autorelease];
[tmpBundle release];
[tmpBundle dealloc];
tmpBundle = nil;
and, after that didn't help:
- Enabling ARC.
=> Why is this eating up memory, although the object (tmpbundle) should get removed?
=> How may I achieve leak-less NSImage usage?
Thanks for any tips/suggestions/solutions :)
Issue
You will have a memory leak if you create your object on your method and not release it inside that method or you have to reference it when you pass it as a parameter by reference : Passing arguments by value or by reference in objective C
Your problem is that you are creating an instance of infoBundle two times, and when you are initializing another instance of it, you are leaving the first one without reference, so it remains in memory, and without connection to remove it (memory leak).
Solution
To make your things easier you should create an instance of your object
#implementation
{
infoBundle* tmpBundle;
}
Use it where ever you need it
-(infoBundle*)returnInfoBundle
{
tmpBundle = [[infoBundle alloc]init];
tmpBundle.track_artwork = [[NSImage alloc]initWithData:[(iTunesArtwork *)[[[iTunes currentTrack] artworks] objectAtIndex:0] rawData]];
[...]
return tmpBundle;
}
-(void)iTunesDidChange
{
tmpBundle = [self returnInfoBundle];
[...]
[imageView setImage:tmpBundle.track_artwork];
}
And when you are finished with that object dealloc will automatically release it if you add it to dealloc method:
- (void) dealloc
{
[tmpBundle release];
tmpBundle = nil;
}
Hope it helps! :)
Just modifiy this line :-
infoBundle* tmpBundle = [[[infoBundle alloc]init]autorelease];
I can’t tell from your code what you are doing in [imageView setImage:tmpbundle.track_artwork]; but you may be having the same problem I had.
I was using
self.imageToDisplay = [UIImage imageNamed:pictFileName];
and kept getting leaks. I switched to
self.imageToDisplay = [UIImage imageWithContentsOfFile:pictFile];
and they went away.
According to the documentation for imageNamed,
This method looks in the system caches for an image object with the
specified name and returns that object if it exists… If you have an
image file that will only be displayed once and wish to ensure that it
does not get added to the system’s cache, you should instead create
your image using imageWithContentsOfFile:. This will keep your
single-use image out of the system image cache, potentially improving
the memory use characteristics of your app.
It sounds like you have either the same or a similar issue.

Nested NSCollectionView With Bindings

I am trying to nest NSCollection view inside of one another. I have tried to create a new project using the Apple Quick Start Guide as a base.
I start by inserting a collection view into my nib, to the view that is automatically added I drag another collection view onto it. The sub-collection view added gets some labels. Here is a picture of my nib:
I then go back and build my models:
My second level model .h is
#interface BPG_PersonModel : NSObject
#property(retain, readwrite) NSString * name;
#property(retain, readwrite) NSString * occupation;
#end
My First level model .h is:
#interface BPG_MultiPersonModel : NSObject
#property(retain, readwrite) NSString * groupName;
#property(retain,readwrite) NSMutableArray *personModelArray;
-(NSMutableArray*)setupMultiPersonArray;
#end
I then write out the implementation to make some fake people within the first level controller(building up the second level model):
(edit) remove the awakefromnibcode
/*- (void)awakeFromNib {
BPG_PersonModel * pm1 = [[BPG_PersonModel alloc] init];
pm1.name = #"John Appleseed";
pm1.occupation = #"Doctor";
//similar code here for pm2,pm3
NSMutableArray * tempArray = [NSMutableArray arrayWithObjects:pm1, pm2, pm3, nil];
[self setPersonModelArray:tempArray];
} */
-(NSMutableArray*)setupMultiPersonArray{
BPG_PersonModel * pm1 = [[BPG_PersonModel alloc] init];
pm1.name = #"John Appleseed";
pm1.occupation = #"Doctor";
//similar code here for pm2,pm3
NSMutableArray * tempArray = [NSMutableArray arrayWithObjects:pm1, pm2, pm3, nil];
return tempArray;
}
Finally I do a similar implementation in my appdelegate to build the multiperson array
- (void)awakeFromNib {
self.multiPersonArray = [[NSMutableArray alloc] initWithCapacity:1];
BPG_MultiPersonModel * mpm1 = [[BPG_MultiPersonModel alloc] init];
mpm1.groupName = #"1st list";
mpm1.personModelArray = [mpm1 setupMultiPersonArray];
(I'm not including all the code here, let me know if it would be useful.)
I then bind everything as recommended by the quick start guide. I add two nsarraycontrollers with attributes added to bind each level of array controller to the controller object
I then bind collectionview to the array controller using content bound to arrangedobjects
Finally I bind the subviews:
with the grouptitle label to representedobject.grouptitle object in my model
then my name and occupation labels to their respective representedobjects
I made all the objects kvo compliant by including the necessary accessor methods
I then try to run this app and the first error I get is: NSCollectionView item prototype must not be nil.
(edit) after removing awakefromnib from the first level model I get this
Has anyone been successful at nesting nscollection views? What am I doing wrong here? Here is the complete project zipped up for others to test:
http://db.tt/WPMFuKsk
thanks for the help
EDITED:
I finally contacted apple technical support to see if they could help me out.
Response from them is:
Cocoa bindings will only go so far, until you need some extra code to make it all work.
When using arrays within arrays to populate your collection view the
bindings will not be transferred correctly to each replicated view
without subclassing NSCollectionView and overriding
newItemForRepresentedObject and instantiating the same xib yourself,
instead of using the view replication implementation provided by
NSCollectionView.
So in using the newItemForRepresentedObject approach, you need to
factor our your NSCollectionViewItems into separate xibs so that you
can pass down the subarray of people from the group collection view to
your inner collection view.
So for your grouped collection view your override looks like this:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
{
BPG_MultiPersonModel *model = object;
MyItemViewController *item = [[MyItemViewController alloc] initWithNibName:#"GroupPrototype" bundle:nil];
item.representedObject = object;
item.personModelArray = [[NSArrayController alloc] initWithContent:model.personModelArray];
return item;
}
And for your inner collection subclass your override looks like this:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
{
PersonViewController *item = [[PersonViewController alloc] initWithNibName:#"PersonPrototype" bundle:nil];
item.representedObject = object;
return item;
}
here is a sample project that they sent back to me -
http://db.tt/WPMFuKsk
I am still unable to get this to work with my own project. Can the project they sent back be simplified further?
Please take a closer look at this answer
Short answer:
Extracting each NSView into its own .xib should solves this issue.
Extended:
The IBOutlet’s specified in your NSCollectionViewItem subclass are not connected when the prototype is copied. So how do we connect the IBOutlet’s specified in our NSCollectionViewItem subclass to the controls in the view?
Interface Builder puts the custom NSView in the same nib as the NSCollectionView and NSCollectionViewItem. This is dumb. The solution is to move the NSView to its own nib and get the controller to load the view programmatically:
Move the NSView into its own nib (thus breaking the connection between the NSCollectionViewItem and NSView).
In I.B., change the Class Identity of File Owner to the NSCollectionViewItem subclass.
Connect the controls to the File Owner outlets.
Finally get the NSCollectionViewItem subclass to load the nib:
Usefull links:
how to create nscollectionview programatically from scratch
nscollectionview tips
attempt to nest an nscollectionview fails
nscollectionview redux

Why is my variable out of scope?

I have an NSMutableArray defined in my AppDelegate class:
NSMutableArray *devices;
I populate the array from a class in the didFinishLaunchingWIthOptions method:
Devices *devs = [[Devices alloc] init];
self.devices = [devs getDevices];
The getDevices method parses a json string and creates a Device object, adding it to the array:
NSMutableArray *retDevices = [[NSMutableArray alloc] initWithCapacity:[jsonDevices count]];
for (NSDiectionary *s in jsonDevices) {
Device *newDevice = [[Device alloc] init];
device.deviceName = [s objectForKey:#"name"];
[retDevices addObject: newDevice];
}
return retDevices;
I then use the AppDelegate class's devices array to populate a tableView. As seen in the cellForRowAtIndexPath method:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Device *d = (Device *)[appDelegate.devices objectAtIndex:indexPath.row];
cell.label.text = d.deviceName;
When the tableview is populated initially, it works. But when I scroll through the list, and the cellForRowAtIndexPath is executed again, d.deviceName throws an error because it has gone out of scope.
Can anyone help me understand why? I'm sure it has something to do with an item being released... but... ??
Thanks in advance.
If it is indeed a problem with memory management, answering these questions should lead you to an answer:
Is deviceName declared as a retain or copy property by the Device interface declaration?
Is -deviceName synthesized? If not, how is it implemented?
Is devices declared as a retain property by the app delegate class's interface declaration?
Is devices synthesized? If not, how is it implemented?
Of course, it might not be a problem with memory management. It would help a lot if you were to provide the actual text of the error message and any provided backtrace.

Accessing property inside non-init methods gives bad access

I've begun work on a side project, so the codebase is very small, very little that could go wrong. Something strange is happening. In viewDidLoad I initialise an array set as a property:
#property (nonatomic, retain) NSMutableArray * story_array;
And fill it with data. This printout is fine:
NSLog(#"%#", ((ArticlePreview *)[self.story_array objectAtIndex:0]).article);
I have a gesture recognizer:
UITapGestureRecognizer * openStory = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showStory)];
Tapping on it calls a method whose first line is this (i.e. the same NSLog):
NSLog(#"%#", ((ArticlePreview *)[self.story_array objectAtIndex:0]).article);
But this causes a bad access. Accessing story_array itself is fine (it'll say it has however many ArticlePreview objects inside) but accessing their fields is a no-no.
The story_array is init'ed as follows:
self.story_array = [[NSMutableArray alloc] init];
Assignment to the fields of the ARticle Preview object were not done properly. I had:
someField = someValue;
I needed:
self.someField = someValue;
I still find that a bit crazy, but there you go. Solved.