How to setup NSArrayController with Core Data? - objective-c

I am trying to programmatically setup an NSArrayController to work with Core Data.
I know that my Core Data store has content since I can manually retrieve objects through the managed object context. I hooked up an NSArrayController to the same managed object context and then bound the value parameter of a NSTableColumn to the NSArrayController.
I asked the NSArrayController to fetch but it returns an empty array.
Any suggestions on what I might be doing wrong?
Interface
#interface MTTableViewController : NSObject <NSTableViewDelegate, NSTableViewDataSource>
{
NSMutableArray *tableData;
MTTableCell *tableCell;
IBOutlet NSTableColumn *tableColumn;
NSArrayController *dataController;
}
Implementation
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
dataController = [[NSArrayController alloc] init];
[dataController setManagedObjectContext:[[NSApp delegate] managedObjectContext]];
[dataController setEntityName:#"Track"];
[dataController setAutomaticallyPreparesContent:YES];
[dataController fetch:self];
NSArray *content = [dataController arrangedObjects];
NSLog(#"Count :%i", (int)[content count]); //Outputs 0
tableCell = [[MTTableCell alloc] initTextCell:#""];
[tableColumn setDataCell:tableCell];
}
return self;
}

The fetch: doesn't wait for data to be loaded, instead it returns instantly.
This makes sense in bindings-enabled environment. You usually have a table view bound to an array controller, which updates whenever controller content changes.
In this case you could observe for changes in arrangedObjects of the controller:
[self.arrayController addObserver:self forKeyPath:#"arrangedObjects" options:NSKeyValueObservingOptionNew context:NULL];
And then:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Data: %#", self.arrayController.arrangedObjects);
}
Source: https://stackoverflow.com/a/13389460

Related

NSTableView + NSArrayController + Core Data with issue "Invalid parameter not satisfying: aString != nil"

I'm having a issue with custom cell view table NSTableView + NSArrayController + Core Data. Saving the items to core data without problems but if i want to refresh the table view after getting notification that NSArrayController changed (init loading and displaying the table are without problems), i get the Exception "Invalid parameter not satisfying: aString != nil" and breakpoint is as in the photo 2. With Debugging i saw that the count of items in arrangedObjects of NSArrayController did change correctly. Why did i get this Exception in the method - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Is it possible that i get the notification of changing in NSArrayController before the saving items in core data done? Somehow some properties of new saving item were nil when i try displaying them on the table?
Some code of my TableController
#interface TableController : NSController <NSTableViewDataSource, NSTableViewDelegate>
#property IBOutlet NSArrayController* arrayController;
#property IBOutlet TableView *tableView;
#property IBOutlet NSSearchField *searchField;
#property PasteboardItem *actualItem;
- (BOOL)isFiltering;
- (BOOL)copySelectedItemToPasteboard;
- (IBAction)clickOnRemoveButton:(id)sender;
- (IBAction)clickOnCopyButton:(id)sender;
#end
#implementation TableController
- (id)init
{
self = [super init];
if (self) {
AppDelegate *appDelegate = (AppDelegate *) [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
_arrayController = [[NSArrayController alloc] init];
[_arrayController setManagedObjectContext:context];
[_arrayController setEntityName:#"PasteboardItem"];
[_arrayController fetch:self];
[_arrayController addObserver:self forKeyPath:#"arrangedObjects" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"arrangedObjects"]) {
[_tableView reloadData];
}
}
Table View with two different custom cell views:
Exception with break point:
Saving to core data is OK:

Reloading NSTableView data from a AFNetworking callback

Edit: as it turned out I was going completely the wrong way with this. I discovered that when I tried to wire a button to call a method on the controller, it still crashed, even when that controller action did absolutely nothing. This led me to wonder, and I realized in my main window controller, I was creating another controller and then moving on with no references to that new controller. So essentially my crash was due to the view controller being deallocated, not anything to do with reloading the table or fetching data. I've solved this by adding a strong property for the child view controller in the main window controller, though I'm not sure this is 100% the best strategy.
Driving me slightly batty here... I'm trying to do an AFNetworking call and reload an NSTable when I get the results. This, however, crashes with rather unhelpful errors. I am guessing that this is due to a threading issue, but various methods I've tried to avoid this (such as using __block) haven't helped. I'm at a loss as to how to get this to work.
Here is the hopefully trimmed down relevant code.
// Controller.h
#property (strong, nonatomic) NSMutableArray *messages;
// Controller.m
#synthesize messages = _messages;
- (id)initWithNibName:(NSString *)nibNameOrNil room: (Room *) theRoom bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_messages = [[NSMutableArray alloc] init];
}
}
- (void)loadView {
[super loadView];
[self updateData];
}
- (void) updateData {
...
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *responseObjs = responseObject[#"messages"];
for (NSDictionary *message in responseObjs) {
Message *m = [[Message alloc] init];
m.property = message[#"property];
...
[_messages addObject: m];
}
[messagesTableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (NSInteger) numberOfRowsInTableView:(NSTableView *)tableView {
return [_messages count];
}
- (NSView *) tableView: (NSTableView *) tableView viewForTableColumn: (NSTableColumn *) tableColumn row:(NSInteger) row {
NSTableCellView *cellView = [tableView makeViewWithIdentifier:#"MainCell" owner: self];
[cellView.textField setStringValue: #"foo"];
return cellView;
}
The error I'm getting is: Thread 1: EXC_BAD_ACCESS (code=EXC_i386_GPFLT)
Any clues? If I skip the reloadData call then it runs without crashing, though obviously shows no data.

Key value observing with Array not doing anything

I have two views, my first view class1.m and second view is class2.m. My second view is initialized as a popover when a button is pressed on a toolbar in the first view. I have an array in my second view, in which objects is added, if any of the rows is pressed. I'm trying to set up a KVO in my first view, so that i can access the allSelectedFocus array from the second view in my first view, but it's not working. I realize that i don't invoke removeObserver, but i don't know where to invoke it, without it removing the observer before it's used. If anybody know any better ways to do this, I'm open for suggestions, but if someone can get this to work, that would be really awesome too.
//class2.m
#import "class2.h"
#import "class1.h"
#implementation class2
#synthesize selectedFocus = _selectedFocus;
#synthesize focusArray = _focusArray;
#synthesize allSelectedFocus = _allSelectedFocus;
- (void)viewDidLoad
{
_focusArray = [[NSArray alloc]initWithObjects:#"Balance",#"Bevægelse",#"Elementskift",#"Vejrtrækning",#"Alle",nil];
[super viewDidLoad];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return _focusArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *cellValue = [_focusArray objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
_selectedFocus = [[_focusArray objectAtIndex:indexPath.row] stringByAppendingString:#","];
if(![[self mutableAllSelectedFocus] containsObject:_selectedFocus])
{
//add object to array, if it's not already there
[[self mutableAllSelectedFocus] addObject:_selectedFocus];
}
else
{
//remove object from array, if it's already there
[[self mutableAllSelectedFocus] removeObject:_selectedFocus];
}
}
-(NSMutableArray *)allSelectedFocus
{
if(_allSelectedFocus == nil)
{
_allSelectedFocus = [[NSMutableArray alloc]init];
}
return _allSelectedFocus;
}
-(NSMutableArray *)mutableAllSelectedFocus
{
return [self mutableArrayValueForKey:#"allSelectedFocus"];
}
#end
//class1.m
#import "class1.h"
#import "class2.h"
#implementation class1
- (void)viewDidLoad
{
[super viewDidLoad];
if(_focusTag == nil)
{
_focusTag = [[class2 alloc]init];
}
[_focusTag addObserver:self forKeyPath:#"selectedFocus" options:NSKeyValueObservingOptionNew context:NULL];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"allSelectedFocus"])
{
NSLog(#"%#", [object valueForKeyPath:keyPath]);
}
}
I suspect that this is either a function of the fact that NSArray objects are not observable or some broader violation of KVC compliance. Regardless, just implement manual change notification for NSArray objects and you should be fine. I just tested changes to NSArray objects (the adding of objects) and automatic notification did not take place, but when I added manual notification, it worked fine. (Though, curiously, NSKeyValueObservingOptionOld doesn't work as expected, showing the new value instead of the old value.) FYI, here is an example of the update method which adds something to my objects NSMutableArray, using manual notification:
- (void)addToMyArray:(id)obj
{
[self willChangeValueForKey:#"myArray"];
[_myArray addObject:obj];
[self didChangeValueForKey:#"myArray"];
}
Update:
By the way, if you need NSKeyValueObservingOptionOld, you can do something like:
- (void)addToMyArray:(id)obj
{
NSMutableArray *tempArray = [NSMutableArray arrayWithArray:_myArray];
[tempArray addObject:obj];
[self setMyArray:tempArray];
}
This way, you don't need manual notification, and you can retrieve both old and new values, but it also seems like an inefficient use of memory, so there are pros and cons.
Just a heads up, at no point are you using an accessor method to set / get your properties. This means that KVO won't work. I believe KVO relies on the getting / setting of properties via accessors.
I'm not sure of what you're trying to accomplish with the application, but I put together some code that may be of help. I commented within the code so I won't be explaining it throughout this answer.
I'll start with class2 as you did:
#import <UIKit/UIKit.h>
#interface Class2ViewController : UITableViewController
#property (nonatomic, strong) NSArray *focusArray;
#property (nonatomic, strong) NSMutableArray *allSelectedFocus;
// This is a readonly property that will return a mutable array of the allSelectedFocus property
// This gives you the ability to have automatic KVO if you add/remove using this property
// You won't have to wrap your calls to will/didChangeValueForKey:
#property (nonatomic, readonly, strong) NSMutableArray *mutableAllSelectedFocus;
#end
#import "Class2ViewController.h"
#import "Class1ViewController.h"
#implementation Class2ViewController
#synthesize focusArray = _focusArray;
#synthesize allSelectedFocus = _allSelectedFocus;
- (void)viewDidLoad
{
[super viewDidLoad];
// This is what you have
// FYI you are accessing the iVar directly, not sure if that matters in your app or not
_focusArray = [[NSArray alloc] initWithObjects:#"Balance",#"Bevægelse",#"Elementskift",#"Vejrtrækning",#"Alle",nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Grab the string of interest from the _focusArray --> this is direct access again which I imagine is fine
NSString *selectedFocus = [[_focusArray objectAtIndex:indexPath.row] stringByAppendingString:#","];
// Use the new mutableAllSelectedFocus property to check if the array doesn't contain the string of interest
if (![[self mutableAllSelectedFocus] containsObject:selectedFocus]) {
// If it doesn't contain it, add it using the mutableAllSelectedFocus property
[[self mutableAllSelectedFocus] addObject:selectedFocus];
}
}
// This is getter that lazily instantiates your _allSelectedFocus array
- (NSMutableArray *)allSelectedFocus
{
// Check to see if the backing iVar is nil
if (_allSelectedFocus == nil) {
// If it is, create an empty mutable array
_allSelectedFocus = [[NSMutableArray alloc] init];
}
// return the array
return _allSelectedFocus;
}
// This is our new property
- (NSMutableArray *)mutableAllSelectedFocus
{
// mutableArrayValueForKey: returns a mutable array for the given key (property)
// Allows us better KVO and efficiency with changing properties
return [self mutableArrayValueForKey:#"allSelectedFocus"];
}
And now class 1:
#import "Class1ViewController.h"
#import "Class2ViewController.h"
#implementation Class1ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// If you are using ARC, this instance will be deallocated after -viewDidLoad
// You will want to store this in an instance variable if you need to keep it around
Class2ViewController *class2ViewController = [[Class2ViewController alloc] init];
[class2ViewController addObserver:self
forKeyPath:#"allSelectedFocus"
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"allSelectedFocus"]) {
NSLog(#"%#", [object valueForKeyPath:keyPath]);
}
}
I'm not sure if this change in code will be helpful in your application. Two things I would do though would be read the Key-Value Coding and Key-Value Observing Guides if you haven't and read this post on to-many relationships and properties.
If I got something wrong, just leave a comment.
Good luck.

Objective C: Unable to Assign value to Labels

I am trying to access properties of an object (person's firstName) which is stored in an array and assign it to labels in a seperate view Controller (SplitMethodViewController). The name value is successfully assigned here. Code snippet as below:
In the initial view controller (before displaying the modal view controller containing the UILabel):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = [indexPath row];
Person *thisPerson = (Person *)[self.personArray objectAtIndex:row];
SplitMethodViewController *smvc = [[SplitMethodViewController alloc]initWithNibName:nil bundle:nil];
smvc.nameLabel.text = [[NSString alloc] initWithFormat:#"%#", thisPerson.firstName];
//This lines returns the value I want, showing that assignment is working till this point
NSLog(#"The name label is %#", smvc.nameLabel.text);
[self presentModalViewController:smvc animated:YES];
[smvc release];
}
However, the values became blank when I check in the splitMethodViewController (checked in ViewDidLoad Method)
#interface SplitMethodViewController : UIViewController
{
UILabel *nameLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *nameLabel;
#end
#implementation SplitMethodViewController
#synthesize nameLabel;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
self.nameLabel = [[UILabel alloc] init];
}
return self;
}
- (id)init
{
return [self initWithNibName:nil bundle:nil];
}
- (void)viewDidLoad
{
//name label returning nothing here.
NSLog(#"namelabel is %#",self.nameLabel.text);
[super viewDidLoad];
}
#end
I am sure I made some silly mistake somewhere. I have tried deleting all the outlets and labels and re-created just one name label and outlet. But I am still hitting this same issue.
Any help will be appreciated!
Did you actually allocate and instantiate the nameLabel and evenBillAmountLabel once you instantiate the SplitMethodViewController? In Objective-C messages (method calls) can be sent to nil (non-existant objects) without returning any errors, but also without any results.
Make sure the -init method on SplitMethodViewController looks somewhat like this:
// this is the designated initializer of most view controllers,
// do initialization here ...
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self)
{
nameLabel = [[UILabel alloc] init];
evenBillAmountLabel = [[UILabel alloc] init];
// add other stuff you need to initialize ...
}
return self;
}
- (id)init
{
// since we don't wanna re-implement allocation and instantiation for every
// initializer, we call the 'designated initializer' with some default values,
// in this case the default nibName and bundle are nil.
return [self initWithNibName:nil bundle:nil];
}
- (void)dealloc
{
[nameLabel release];
[evenBillAmountLabel release];
[super dealloc];
}
Be sure to read about designated initializers if this is new to you and if this was related to your issue. Here's a link to Apple's documentation on the subject.
If Wolfgang's answer doesn't solve it, be sure that your UILabel references in your SplitMethodViewController.xib file are wired up to the correct referencing outlet in your SplitMethodViewController.h file.

Filtering A Tree Controller

I have now nearly figured out how to Filter a NSTreeController, to do this I have sub-classed NSManagedObject and added some code to my App Delegate, I have also bound my NSSearchField to the filterPredicate of my App Delegate but I think I need to connect my NSTreeController and NSSearchField in some way to make it work.
Below I have posted all the code I have used so far to try and make it work.
NSManagedObject Sub-Class Header File.
#interface Managed_Object_Sub_Class : NSManagedObject {
NSArray *filteredChildren; // this should fix the compiler error
}
- (NSArray *)filteredChildren;
#end
NSManagedObject Sub-Class Implementation File.
#implementation Managed_Object_Sub_Class
static char *FilteredChildrenObservationContext;
- (id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context {
if (self = [super initWithEntity:entity insertIntoManagedObjectContext:context]) {
[[NSApp delegate] addObserver:self forKeyPath:#"filterPredicate" options:0 context:&FilteredChildrenObservationContext];
[self addObserver:self forKeyPath:#"subGroup" options:0 context:&FilteredChildrenObservationContext];
}
return self;
}
// use finalize with GC
- (void)dealloc {
[[NSApp delegate] removeObserver:self forKeyPath:#"filterPredicate"];
[self removeObserver:self forKeyPath:#"subGroup"];
[super dealloc];
}
- (NSArray *)filteredChildren {
if (filteredChildren == nil) {
NSPredicate *predicate = [[NSApp delegate] filterPredicate];
if (predicate)
filteredChildren = [[[self valueForKey:#"subGroup"] filteredArrayUsingPredicate:predicate] copy];
else
filteredChildren = [[self valueForKey:#"subGroup"] copy];
}
return filteredChildren;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &FilteredChildrenObservationContext) {
[self willChangeValueForKey:#"filteredChildren"];
[filteredChildren release];
filteredChildren = nil;
[self didChangeValueForKey:#"filteredChildren"];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#end
Code Added To App Delegate Header File
NSPredicate *filterPredicate;
Code Added To App Delegate Implementation File
- (NSPredicate *)filterPredicate {
return filterPredicate;
}
- (void)setFilterPredicate:(NSPredicate *)newFilterPredicate {
if (filterPredicate != newFilterPredicate) {
[filterPredicate release];
filterPredicate = [newFilterPredicate retain];
}
}
Search Field Binding
alt text http://snapplr.com/snap/vs9q
This doesn't work yet, and so that is why I am asking what I need to do from here to make it work, like I said I think I need to connect the NSSearchField and NSTreeController Together in some way.
Again I hav answered my own question, I also hope that this will help other people so they know how to Filter a NSTreeController.
To make it work from my post above do the following.
1.For your entity set the Class as your NSManagedObject Sub-Class in my Case JGManagedObject.
alt text http://dvlp.me/c3k
2.For your search field in IB set the predicate format to what you want to Filter ( The Property in your entity, for me it is name).
alt text http://dvlp.me/9k9rw