NSCollectionView draws nothing - objective-c

I'm trying to set up an NSCollectionView (I have done this successfully in the past, but for some reason it fails this time).
I have a model class called "TestModel", and it has an NSString property that just returns a string (just for testing purposes right now). I then have an NSMutableArray property declaration in my main app delegate class, and to this array I add instances of the TestModel object.
I then have an Array Controller that has its Content Array bound the app delegate's NSMutableArray. I can confirm that everything up to here is working fine; NSLogging:
[[[arrayController arrangedObjects] objectAtIndex:0] teststring]
worked fine.
I then have all the appropriate bindings for the collection view set up, (itemPrototype and content), and for the Collection View Item (view). I then have a text field in the collection item view that is bound to Collection View Item.representedObject.teststring. However NOTHING displays in the collection view when I start the app, just a blank white screen. What am I missing?
UPDATE: Here is the code I use (requested by wil shipley):
// App delegate class
#interface AppController : NSObject {
NSMutableArray *objectArray;
}
#property (readwrite, retain) NSMutableArray *objectArray;
#end
#implementation AppController
#synthesize objectArray;
- (id)init
{
if (self = [super init]) {
objectArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
TestModel *test = [[[TestModel alloc] initWithString:#"somerandomstring"] autorelease];
if (test) [objectArray addObject:test];
}
#end
// The model class (TestModel)
#interface TestModel : NSObject {
NSString *teststring;
}
#property (readwrite, retain) NSString *teststring;
- (id)initWithString:(NSString*)customString;
#end
#implementation TestModel
#synthesize teststring;
- (id)initWithString:(NSString*)customString
{
[self setTeststring:customString];
}
- (void)dealloc
{
[teststring release];
}
#end
And then like I said the content array of the Array Controller is bound to this "objectArray", and the Content of the NSCollectionView is bound to Array Controller.arrangedObjects. I can verify that the Array Controller has the objects in it by NSLogging [arrayController arrangedObjects], and it returns the correct object. Its just that nothing displays in the NSCollectionView.
UPDATE 2: If I log [collectionView content] I get nothing:
2009-10-21 08:02:42.385 CollViewTest[743:a0f] (
)
The problem is probably there.
UPDATE 3: As requested here is the Xcode project:
http://www.mediafire.com/?mjgdzgjjfzw
Its a menubar app, so it has no window. When you build and run the app you'll see a menubar item that says "test", this opens the view that contains the NSCollectionView.
Thanks

The problem is that your not correctly using KVC. There is two things you can do.
Method 1: Simple but not so elegant
Use the following code to add the object to the array
[[self mutableArrayValueForKey:#"objectArray"] addObject:test];
This isn't so elegant as you have to specify the variable using a string value, so you will not get compiler warnings when spelt incorrectly.
Method 2: Generate the KVO methods needed for the array "objectArray".
Select the property in your interface declaration
Select Scripts (the script icon in the menubar) > Code > Place
accessor decls on Clipboard
Paste the declarations in the
appropriate spot in your interface file
Select Scripts > Code > Place
accessor defs on Clipboard
Paste the definitions in the
appropriate spot in your implementation file
You can then use a method that looks like
[self insertObject:test inObjectArrayAtIndex:0];

Related

NSMutableArrays are null

I have an NSViewController subclass with:
#property (retain) NSMutableArray* entities;
#property (retain) NSMutableArray* tiles;
In my -init method, both arrays are created with +new, and are given one object each. After that, I call NSLog(#"%#, %#", entities, tiles);, and it gives me just as expected:
2012-12-30 15:07:04.160 Project Land III[2177:303] (
"<RBEntity: 0x100508170>"
), (
"<RBTile: 0x100508470>"
)
I can click a button on the view, though, which calls the same log function, and it spit out this:
2012-12-30 15:07:06.071 Project Land III[2177:303] (null), (null)
I've been stuck on this problem in some form or another for days. Why in the world are the arrays null?
I'm more than happy to post more code, just let me know!
Interface:
#import <Cocoa/Cocoa.h>
#import "RBEntity.h"
#import "RBTile.h"
#interface RBMainViewController : NSViewController {
NSMutableArray* _entities;
NSMutableArray* _tiles;
}
#property (retain) NSMutableArray* entities;
#property (retain) NSMutableArray* tiles;
- (IBAction)log:(id)sender;
#end
My -init method:
- (id)init {
self = [super init];
self.entities = [NSMutableArray new];
self.tiles = [NSMutableArray new];
[self.entities addObject:[RBEntity entityWithLocation:NSMakePoint(4, 5) type:FACEEATER]];
[self.tiles addObject:[RBTile tileWithLocation:NSMakePoint(10, 2) type:GRASS]];
NSLog(#"%#, %#", self.entities, self.tiles);
return self;
}
In my -init method, both arrays are created with +new, and are given one object each. After that, I call NSLog(#"%#, %#", entities, tiles);, and it gives me just as expected:
2012-12-30 15:07:04.160 Project Land III[2177:303] (
"<RBEntity: 0x100508170>"
), (
"<RBTile: 0x100508470>"
)
I can click a button on the view, though, which calls the same log function, and it spit out this:
2012-12-30 15:07:06.071 Project Land III[2177:303] (null), (null)
This is a very common novice mistake.
You have two RBMainViewController objects. One of them, you presumably created in code in one of your other .m files, by saying something like [[RBMainViewController alloc] init]. The other, you created in a nib, probably by dragging it into the nib.
(Note: The nib that you created that VC in is not the VC's nib. That would be circular, to have the VC's nib containing the VC that is loading the nib. The VC that doesn't have its arrays resides in nib A, and each VC will load nib B.)
The VC that you created in a nib is the one whose view appears on the screen. Because that object never received an init message (it was initialized with some other initWith… message instead), you never created its arrays. The view controller you created with init, which does have its arrays, is not visible on the screen (otherwise you would have clicked on its button, rather than the other's, and you'd have seen the arrays in the output).
The solution involves two changes.
The first is to change your implementation of init to be an implementation of initWithNibName:bundle: instead. Like so:
- (instancetype) initWithNibName:(NSString *)nibName
bundle:(NSBundle *)bundle
{
self = [super initWithNibName:nibName bundle:bundle];
if (self != nil) {
<#...#>
}
return self;
}
If you want to continue using init to create your VC in other code, fine, but your implementation of -[RBMainViewController init] should simply send initWithNibName:bundle: to self and return the result.
- (instancetype) init {
return [self initWithNibName:<#nibName#> bundle:<#bundle#>];
}
You also need to delete one of the two view controllers. We'd need to see the code and the nib to know which. If you delete the one you created in code, you may want to create an outlet in that class and connect it in the nib. If you delete the one in the nib, any outlet connections you established in that nib to that VC, you'll need to re-create in code.
Write a custom getter/setter for one of these properties, and put a breakpoint there to see who's resetting the value. If that doesn't catch the problem, and your property is still being reset, you're probably accessing them from an instance of your NSViewController that hasn't been initialised by your init, thus those properties were always uninitialised.
I believe you will need to use self.entites and self.tiles when working with those objects in your class.
EDIT (after interface added)
You will need to have something in your interface like:
#interface RBMainViewController : NSViewController{
NSMutableArray* _entities;
NSMutableArray* _tiles;
}
#property (retain) NSMutableArray* entities;
#property (retain) NSMutableArray* tiles;
- (IBAction)log:(id)sender;
#end
Then you will need to add this to the implementation:
#synthesize entities = _entities;
#synthesize tiles = _tiles;

Objective-c: Singleton - passing variables

I have a singleton that I'd like to use to manage the onscreen animation of my views. Here's my.
#import <Foundation/Foundation.h>
#interface OAI_AnimationManager : NSObject {
NSMutableDictionary* sectionData;
}
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
.m file
#import "OAI_AnimationManager.h"
#implementation OAI_AnimationManager
#synthesize sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
static OAI_AnimationManager* sharedAnimationManager;
#synchronized(self) {
if (!sharedAnimationManager)
sharedAnimationManager = [[OAI_AnimationManager alloc] init];
return sharedAnimationManager;
}
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", sectionData);
}
#end
You'll see in the .h file I added a NSMutableDictionary and am using #property/#synthesize for it's getter and setter.
In my ViewController I instantiate the animation manager as well as a series of subclasses of UIView called Section. With each one I store the data (x/y w/h, title, etc.) in a dictionary and pass that to the dictionary delcared in animation manager. In the Section class I also instantiate animation manager and add a UITapGestureRecognizer which calls a method, which passes along which section was tapped to a method (checkToggleStatus) in animation manager.
As you can I see in the method I am just logging sectionData. Problem is I am getting null for the value.
Maybe my understanding of singletons is wrong. My assumption was the class would only be instantiated once, if it was already instantiated then that existing object would be returned.
I do need all the other Section classes data as if one animates others animate in response and I can get around it by passing the tapped Section to the animation manager and doing [[Section superview] subviews] and then looping and getting the data from each that way but it seems redundant since that data is available in the ViewController when they are created.
Am I doing something wrong in trying to transfer that data? Is there a better solution? I am open to suggestions and criticisms.
Thanks
h file
#interface OAI_AnimationManager : NSObject
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
m file
static OAI_AnimationManager* _sharedAnimationManager;
#implementation OAI_AnimationManager
#synthesize sectionData = _sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
#synchronized(self) {
if (!_sharedAnimationManager) {
_sharedAnimationManager = [[OAI_AnimationManager alloc] init];
}
}
return _sharedAnimationManager;
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", _sectionData);
}
#end
Notice I moved your sectionData variable from the header and moved it to the implementation file. A while back, they changed it to where you can synthesize properties and specify their instance variable names along side it... hence:
sectionData = _sectionData;
I also added and underscore to the instance variable... this is a universal convention for private variables and it also will throw a compile error now if you try to type just sectionData as you did in the return statement of checkToggleStatus:. Now you either have to type self.sectionData or _sectionData.
You didn't include the code that creates an instance of your dictionary but I bet you didn't set it as self.sectionData = [[NSDictionary alloc] init] which means it would not retain the value and you would get null the next time you called it. Classic memory management mistake... I know it well because I learned the hard way hehehe

share NSArray between different UIViewControllers

I am making an application that uses a webService to get data in a JSON format... I get the data I parse them into a object NSArray ... and i use it .. it works fine ...
Now, if the user clicks a button I need to send him to an other Uiview ... which contains more data about the clicked object ..
The problem is here ... I don't want to request again and download the result from the server ... because i already did ... All I want is to have access to that NSArray that I have in the first UIViewController.
You can add on AnotherView.h another property:
#property (nonatomic, retain) NSArray *jsonData;
On AnotherView.m synthesize it. When you are going to to call AnotherView from InitialView, you can set jsonData with the data you retrieved on InitialView.
Create a custom initializer in your other view controller like so:
#import <UIKit/UIKit.h>
#interface OtherViewController : UIViewController
#property (nonatomic, strong) NSArray *myArray;
- (id)initWithArray:(NSArray *)anArray;
#end
Then implement it like so:
#import "OtherViewController.h"
#implementation OtherViewController
#synthesize myArray=_myArray;
- (id)initWithArray:(NSArray *)anArray {
if (!(self = [self initWithNibName:#"OtherViewController" bundle:nil]))
return nil;
if (!anArray) {
#throw [NSException exceptionWithName:#"OtherViewControllerBadInitCall" reason:#"array is nil" userInfo:nil];
}
_myArray = anArray;
return self;
}
//...
#end
You can then init and display your controller like so:
OtherViewController *otherViewController = [[OtherViewController alloc] initWithArray:greatJSONArray];
[self.navigationController pushViewController:otherViewController animated:YES];
There you go.
You can set the array as the property. You can either create a new class and set the array as the property and after you fetch the array, set the property. Or, you can create a property of the existing UIVIewController Class and pass the object.
Either way, you have to set property.
You could define a new property in your second ViewController that holds an NSArray and pass the firt array to the second ViewController before show it.
Well you have not outlined whether you send the data forward or backward. In the later case you will need to implement protocol and delegate(Define your own protocol) but for the prior case you just need to create the property of the Object you want to access in any other class. In case of web-services it is better to use protocol and delegates if u abide by the norms of MVC architecture.

Variables in separate class coming back null

Ok, I think the question I had here was long-winded and difficult to get through. I'll simplify my question:
I have a class called InController.
InController has a method called nextPage that tells an int variable, inPageNumber, to add one onto itself and to call on another InController method called updateTable.
updateTable clears a table, inTable, of its current data and fills it with data relevant to the page number it retrieves from inPageNumber.
The table, inTable, is contained inside an NSBox with specific printing requirements.
I subclassed NSBox into a class called CustomViewPagination to meet these printing requirements, overriding its paginations methods. Basically, when a new printing page is required, it attempts to print the same area again, but calls on nextPage to fill the table with the data of the sequential page.
With me so far?
One of the pagination methods I overrided in CustomViewPagination, beginPageInRect, is automatically called for each printed page by default. Because of this, I placed a call to my InController method of nextPage, to change the inTable data for the current printing page.
My problem is when I call nextPage (which is a method in InController) from my CustomViewPagination class. It does nothing and when I debug it I find that all the variables required in the method are nil. However, they are the correct values when I call nextPage from inside InController.
File Extracts:
InController.h:
#import <Cocoa/Cocoa.h>
#import "CustomViewPagination.h"
#interface InController : NSObject {
IBOutlet NSWindow *inPreview;
IBOutlet CustomViewPagination *inSheet;
NSArray *iSelectedIn;
NSMutableArray *records;
int inPageNumber;
}
#property (nonatomic, retain) NSArray *iSelectedIn;
#property (nonatomic, retain) NSMutableArray *records;
InController.m:
#import "InController.h"
#implementation InController
#synthesize iSelectedIn, records;
- (IBAction) inNextPage:(id)sender {
inPageNumber = inPageNumber + 1;
NSLog(#"inPageNumber called ok");
[self updateIn];
}
- (IBAction)updateInvoice:(id)sender {
//wipe all current records and refresh empty table
[records removeAllObjects];
[inPreviewTable reloadData];
for (NSArray *s in [[iSelectedIn valueForKey:#"inJobList"] lastObject]) {
NSString *jLT = [s valueForKey:#"inJT"];
NSString *jLH = [s valueForKey:#"inJHo"];
NSString *jLC = [s valueForKey:#"inJC"];
// etc.
// if CustomViewPagination called this, records is nil, so nothing
// is cleared, and there's no *s for iSelectedIn as iSelectedIn
// is found to be nil. If InController called this, it works fine.
CustomViewPagination.h:
#import <Cocoa/Cocoa.h>
#class InController;
#interface CustomViewPagination : NSBox {
InController *inControllerInstance;
}
#end
CustomViewPagination.m:
#import "CustomViewPagination.h"
#import "InController.h"
#implementation CustomViewPagination
- (void) awakeFromNib {
inControllerInstance = [[InController alloc] init];
}
- (void)beginPageInRect:(NSRect)aRect atPlacement:(NSPoint)location {
int pageCounter = [[NSPrintOperation currentOperation] currentPage];
if (pageCounter == 1) {
// Don't respond to 1st page, do nothing.
} else {
[inControllerInstance inNextPage:self];
}
[super beginPageInRect:aRect atPlacement:location];
}
#end
You are using 2 IBOutlets in InController (inPreview & inSheet), but InController is created programmatically in CustomViewPagination's awakeFromNib.
How are the Outlets connected? (Can't be from within IB, as you are creating the InController instance programmatically). This would be an explanation why both are nil.

How to add an object to a programmatically bound NSMutableArray?

I have an NSDocument which has the following structure:
#interface MyDocument : NSDocument
{
NSMutableArray *myArray;
IBOutlet NSArrayController *myArrayController;
IBOutlet MyView *myView;
}
#end
I instantiate the NSArrayController and the MyView in MyDocument.xib, and have made the connections to the File's Owner (MyDocument), so I am pretty sure that from the point of view of Interface Builder, I have done everything correctly.
The interface for MyView is simple:
#interface MyView : NSView {
NSMutableArray *myViewArray;
}
#end
Now, in MyDocument windowControllerDidLoadNib, I have the following code:
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
[myArrayController setContent:myArray];
// (This is another way to do it) [myArrayController bind:#"contentArray" toObject:self withKeyPath:#"myArray" options:nil];
[myView bind:#"myViewArray" toObject:myArrayController withKeyPath:#"arrangedObjects" options:nil];
}
In the debugger, I have verified that myViewArray is an NSControllerArrayProxy, so it would appear that my programmatic binding is correct. However, when I try to add objects in MyView's methods to the MyView myViewArray, they do not appear to update the MyDocument's myArray. I have tried both of the following approaches:
[myViewArray addObject:value];
[self addMyViewArraysObject:value];
(The second approach causes a compiler error, as expected, but I thought that the Objective-C runtime would "implement" this method per my limited understanding of KVO.)
Is there something wrong with how I'm trying to update myViewArray? Is there something wrong with my programmatic binding? (I am trying to do this programmatically, because MyView is a custom view and I don't want to create an IB palette for it.)
The problem is that you're mutating your array directly. Implement indexed accessor methods and call those.
KVO overrides your accessor methods (as long as you conform to certain formats) and posts the necessary notifications. You don't get this when you talk directly to your array; anything bound to the property won't know that you've changed the property unless you explicitly tell it. When you use your accessor methods, KVO tells the other objects for you.
The only time to not use your accessor methods (synthesized or otherwise) is in init and dealloc, since you would be talking to a half-inited or -deallocked object.
Once you're using your own accessor methods to mutate the array, and thereby getting the free KVO notifications, things should just work:
The view, when mutating its property, will automatically notify the array controller, which mutates its content property, which notifies your controller.
Your controller, when mutating its property, will automatically notify the array controller, which mutates its arrangedObjects property, which notifies the view.
I can see two possibilities here:
First, do you instantiate the NSMutableArray object (and release it) in your MyDocument class? It should look something like this:
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
myArray = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
- (void)dealloc
{
[myArray release];
[super dealloc];
}
Second, did you declare myViewArray as a property in MyView? It should look something like this:
// MyView.h:
#interface MyView : NSView
{
NSMutableArray * myViewArray;
}
#property (assign) NSMutableArray * myViewArray;
#end
// MyView.m:
#implementation MyView
#synthesize myViewArray;
#end
Other than that, it looks to me like you have done all of the binding properly.
update: How about using the NSArrayController to add items to the array:
// MyView.h:
#interface MyView : NSView
{
NSMutableArray * myViewArray;
IBOutlet NSArrayController * arrayController;
}
#property (assign) NSMutableArray * myViewArray;
- (void)someMethod;
#end
// MyView.m:
#implementation MyView
#synthesize myViewArray;
- (void)someMethod
{
id someObject = [[SomeClass alloc] init];
[arrayController addObject:[someObject autorelease]];
}
#end
The problem appears to be that I had been binding MyView's myViewArray to the NSArrayController's arrangedObjects property instead of its content property.
When binding to arrangedObjects, I found that the actual object pointed to by myViewArray was an instance of NSControllerArrayProxy. I didn't find a definitive answer as to what this object actually does when I searched online for more information on it. However, the code examples I found suggest that NSControllerArrayProxy is intended to expose conveniences for accessing the properties of objects in the array, rather than the objects (in the array) themselves. This is why I believe that I was mistaken in binding to arrangedObjects.
The solution was to instead bind MyView's myViewArray to the NSArrayController's content property:
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
[myArrayController setContent:myArray];
[myView bind:#"myViewArray" toObject:myArrayController withKeyPath:#"content" options:nil];
}
Although this appears to work, I am not 100% sure that it is correct to bind to content in this case. If anyone can shed some light on programmatically binding to the various properties of an NSArrayController, I would welcome comments to this answer. Thanks.
First of all, there's nothing wrong with binding to arrangedObjects: an NSTableColumn, for instance, should have its content bound to arrangedObjects only, and its contentValues to arrangedObjects.someProperty.
The common mistake is to regard arrangedObjects as the content of an arrayController but that, as you have seen, will lead to grief: arrangedObjects is a representation of the way the arrayController has currently arranged the objects in its content, not the content itself.
That said, the way to bind an array to an arrayController is:
[self.myArrayController bind:NSContentArrayBinding
toObject:self
withKeyPath:#"myView.myViewArray"
options:nil];
Are you sure, by the way, your view needs to hold the myViewArray? That usually falls under the responsibility of a controller or model object.
Now you can add objects by calling addObject on the arrayController, since that is the controller's responsibility.
[self.myArrayController addObject: anObject]