Objective C: Filtering Core Data Information based on Set of Annotations visible in current MapRect - objective-c

I have around 1000+ core data objects for my application. The application consist of a mapView and a tableView.
I am interested in displaying only the annotations that are appearing in the current mapRect. Hence I am using the method
NSSet *nearbySet = [self.mapView annotationsInMapRect:self.mapView.visibleMapRect];
The above method will return me a set of annotations available in the current mapView.
Now I only want to display the information based on the annotations returned from the above method in the table view (CoreData Table). How can I query core data for this? Can I use the predicate method with the NSFetchResultController? Any advice on this will be greatly appreicated!
Zhen Hoe
EDIT (10.05.2011):
My location data model is as such:
#dynamic uniqueID;
#dynamic name;
#dynamic address;
#dynamic longitude;
#dynamic latitude;
I have implemented the CoreDataTableViewController as shown below:
- (NSFetchedResultsController *)fetchedResultsControllerForTableView:(UITableView *)tableView
{
if (tableView == self.tableView)
{
if (self.fetchedResultsController.fetchRequest.predicate != normalPredicate)
{
[NSFetchedResultsController deleteCacheWithName:self.fetchedResultsController.cacheName];
self.fetchedResultsController.fetchRequest.predicate = normalPredicate;
[self performFetchForTableView:tableView];
}
[currentSearchText release];
currentSearchText = nil;
}
}
I was thinking of modifying the line 'self.fetchedResultsController.fetchRequest.predicate = normalPredicate" to restrict the fetch to only the objects found in my nearbySet. Is this a good place to do it? Also if you can give me some tips on how to write the predicate part, that will be great!
Thanks
Zhen Hoe

Related

Cocoa Bindings - NSTableView - Swapping Values

Is an NSValueTransform subclass a good choice for displaying Core Data attributes into UI views displaying:
A number string like (0,1,2,3,etc) into a string such as (Pending, Completed, Frozen, In progress, etc)
A number string like (0,1) into a app-based image (red.png if 0, green.png if 1)
Here's what Core Data displays for the two attributes, timer and status:
Here is what I want to be displayed instead, without changing the values in Core Data:
If not to use NSValueTransformer, in what other way is this possible?
I do not want to see the data permanently converted, only for the benefit of less data stored in Core Data and better UI view items.
I have also tried to modify the attributes in the managed object class (with out KVO notification) with no luck.
Yes, NSValueTransformer subclasses work just fine for this purpose.
You can also add read-only computed properties to your managed object class, and that should work, too. Those properties can even be added by a category in the controller code, if they don't make sense as part of the model code.
For example:
+ (NSSet*) keyPathsForValuesAffectingStatusDisplayName
{
return [NSSet setWithObject:#"status"];
}
- (NSString*) statusDisplayName
{
NSString* status = self.status;
if ([status isEqualToString:#"0"])
return #"Pending";
else if ([status isEqualToString:#"1"])
return #"Completed";
// ...
}
The +keyPathsForValuesAffectingStatusDisplayName method lets KVO and bindings know that when status changes, so does this new statusDisplayName property. See the docs for +keyPathsForValuesAffectingValueForKey: to learn how that works.
I ended up using what at first appeared to be blocking the display of different info in those cells, using:
#pragma mark - Table View Delegate
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
/* tableColumn = (string) #"AutomaticTableColumnIdentifier.0"
row = (int) 0 */
NSString *identifier = [tableColumn identifier];
NSTableCellView *cellView = [tableView makeViewWithIdentifier:identifier owner:self];
NSManagedObject *item = [self.itemArrayController.arrangedObjects objectAtIndex:row];
if ([identifier isEqualToString:#"AutomaticTableColumnIdentifier.0"]) {
/* subviews returns array with 0 = Image View &
1 = Text Field */
/* First, set the correct timer image */
... logic ...
NSImageView *theImage = (NSImageView *)[[cellView subviews] objectAtIndex:0];
theImage.image = [NSImage imageNamed:#"green.gif"];
/* Second, display the desired status */
NSTextField *theTextField = (NSTextField *)[[result subviews] objectAtIndex:1];
... logic ...
theTextField.stringValue = #"Pending";
}
return cellView;
}
Apple's documentation states (somewhere) that bindings with an Array Controller can work in combination with manually populating the table view cells. It seems best and easiest to start with bindings and then refine display values manually.

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

When does objectValueForTableColumn: get called?

I am currently going through the 3rd Edition of the Mac OSX Cocoa book from the Big Nerd Ranch guys and I am editing my program to include table views. Here the code from the AppController.m file where I have to implement the required protocol methods from the TableView:
-(id)init{
self = [super init];
voiceArray = [NSSpeechSynthesizer availableVoices];
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth setDelegate:self];
return self;
}
-(int)numberOfRowsInTableView:(NSTableView *)tv{
NSLog(#"Getting number of rows in table view: %lu", [voiceArray count]);
return [voiceArray count];
}
-(id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
NSString *v = [voiceArray objectAtIndex:row];
NSDictionary *dict = [NSSpeechSynthesizer attributesForVoice:v];
NSLog(#"Voice Name %#", [dict objectForKey:NSVoiceName]);
return [dict objectForKey:NSVoiceName];
}
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
int row = [tableView selectedRow];
NSLog(#"row selected %d", row);
if(row == 1){
return;
}
NSString *selectedVoice = [voiceArray objectAtIndex:row];
[speechSynth setVoice:selectedVoice];
NSLog(#"new voice %# ", selectedVoice);
}
When the app first loads I get the following output:
2012-05-27 15:02:29.040 Speakline[42836:f03] Getting number of rows in
table view: 24 2012-05-27 15:02:29.042 Speakline[42836:f03] row
selected 2 2012-05-27 15:02:29.043 Speakline[42836:f03] new voice
com.apple.speech.synthesis.voice.Alex 2012-05-27 15:02:29.162
Speakline[42836:f03] Voice Name Agnes 2012-05-27 15:02:29.163
Speakline[42836:f03] Voice Name Albert
I want to make sure I understand fully what is going on here. In order to do this I have a couple of questions.
It looks like the numberOfRowsInTableView: method was automatically called after the init method. Is this correct?
How often does objectValueForTableColumn: get called? What events prompt that method to get called? Also, in the code there, the return statement confused me. Where exactly does this return value go?
As a side note they wanted me to connect the Outlets and the AppController via control+clicking (via the connections panel) and linking them in that way. What alternatives are there avaialble for connecting delegates and datasources to different kinds of views without doing this? I am assuming that adding NSTableViewDelegate in the controller header file might be one way. If you have the option of control+click connecting all your views to outlets and so on vs programmatically setting it all up is it just a matter of preference at this point? It just seems to me that in order to understand what is going on it might be better to just write the code yourself.
1) Both numberOfRowsInTableView and objectValueForTableColumn:Row: get called soon after the nib is unarchived (I'm not sure of the exact order of things), so the table view can be populated with data.
2) The loading of the table and reloading or adding new data as well as scrolling will cause objectValueForTableColumn:row: to be called (by the table view). It gets called for each row that is populated. The value goes to the table view, that's how it gets its data.
3) The other way to connect delegates is to do it in code with setDelegate:, but putting NSTableViewDelegate in the .h file does not do that -- that's just a promise to the compiler that you will implement some or all of the delegate messages.

Passing array of custom objects to NSCell in NSMatrix programmatically

I have an NSArray of custom NSObjects. Each object has some properties and an image that I would like to display in a grid view. NSMatrix appears to be a good solution to my problem, but I am having issues getting the content of the objects to display.
Couple of things to note.
I am not using core data
I am trying to do this programmatically
I have considered using NSCollectionView but NSMatrix appears to be a better solution in this case
All the cells follow the same display format as each other - i.e. I'm not wanting to pass different cells different types of objects, just a different instance of the object
Assume I have an NSView (matrixContainerView) in a window. The controller file has an IBOutlet to matrixContainerView. In my controller I have the following in my awakeFromNib:
NSMatrix* matrix = [[NSMatrix alloc]
initWithFrame:[matrixContainerView bounds]
mode:NSRadioModeMatrix
cellClass:[MyCustomCell class]
numberOfRows:5
numberOfColumns:5];
[matrix setCellSize:NSMakeSize(116, 96)];
[matrix setNeedsDisplay:YES];
[matrixContainerView addSubview:[matrix autorelease]];
[matrixContainerView setNeedsDisplay:YES];
The class MyCustomCell header looks like the following:
#interface MyCustomCell : NSCell {
MyModel * theObject;
}
-(MyModel *)theObject;
-(void)setTheObject:(MyModel *)newValue;
And the implementation file as follows (drawing simplified):
#implementation MyCustomCell
-(void)drawInteriorWithFrame:(NSRect)theFrame inView:(NSView *)theView {
...drawing code using MyModel e.g. [MyModel isValid] etc...
}
-(MyModel *)theObject {
return theObject;
}
-(void)setTheObject:(MyModel *)newValue {
[theObject autorelease];
theObject = [newValue retain];
}
#end
After some initialization and population of the array containing MyModel objects in the controller, I want to populate the NSMatrix with instances of the objects.
How do I do this?
I have tried adding just two objects from the array as follows (just as a test):
MyCustomCell * cellOne = (MyCustomCell *)[matrix cellAtRow:0 column:0];
[cell setTheObject:[myArrayOfObjects objectAtIndex:0]];
MyCustomCell * cellTwo = (MyCustomCell *)[matrix cellAtRow:0 column:1];
[cellTwo setTheObject:[myArrayOfObjects objectAtIndex:1]];
But this just creates the first object image. If the above had worked, it would been a straightforward task of enumerating through the array and adding the objects.
How do I go about adding the cells and passing the appropriate objects to those cells in order that they can be displayed correctly?
The Apple docs are sparse to say the least on NSMatrix as far as the programming guide goes. The information in there is very useful to me, but only after I have added the objects and got them displaying!
Update
If I do not add the two objects (as per my example above) the output is no different, i.e. a single representation of my custom cell is drawn to screen. This tells me that the single representation I see is being done at the initialization of the matrix and in fact I wasn't drawing anything to column 0 row 0 when in fact I thought I was. Which leaves me now more confused.
Might be that the matrix actually has the two cells but its frame is too small to display them?
After adding the cells try calling [matrix sizeToCells]

Exposing model object using bindings in custom NSCell of NSTableView

I am struggling trying to perform what I would think would be a relatively common task. I have an NSTableView that is bound to it's array via an NSArrayController. The array controller has it's content set to an NSMutableArray that contains one or more NSObject instances of a model class. What I don't know how to do is expose the model inside the NSCell subclass in a way that is bindings friendly.
For the purpose of illustration, we'll say that the object model is a person consisting of a first name, last name, age and gender. Thus the model would appear something like this:
#interface PersonModel : NSObject {
NSString * firstName;
NSString * lastName;
NSString * gender;
int * age;
}
Obviously the appropriate setters, getters init etc for the class.
In my controller class I define an NSTableView, NSMutableArray and an NSArrayController:
#interface ControllerClass : NSObject {
IBOutlet NSTableView * myTableView;
NSMutableArray * myPersonArray;
IBOutlet NSArrayController * myPersonArrayController;
}
Using Interface Builder I can easily bind the model to the appropriate columns:
myPersonArray --> myPersonArrayController --> table column binding
This works fine. So I remove the extra columns, leaving one column hidden that is bound to the NSArrayController (this creates and keeps the association between each row and the NSArrayController) so that I am down to one visible column in my NSTableView and one hidden column. I create an NSCell subclass and put the appropriate drawing method to create the cell. In my awakeFromNib I establish the custom NSCell subclass:
MyCustomCell * aCustomCell = [[[MyCustomCell alloc] init] autorelease];
[[myTableView tableColumnWithIdentifier:#"customCellColumn"]
setDataCell:aCustomCell];
This, too, works fine from a drawing perspective. I get my custom cell showing up in the column and it repeats for every managed object in my array controller. If I add an object or remove an object from the array controller the table updates accordingly.
However... I was under the impression that my PersonModel object would be available from within my NSCell subclass. But I don't know how to get to it. I don't want to set each NSCell using setters and getters because then I'm breaking the whole model concept by storing data in the NSCell instead of referencing it from the array controller.
And yes I do need to have a custom NSCell, so having multiple columns is not an option. Where to from here?
In addition to the Google and StackOverflow search, I've done the obligatory walk through on Apple's docs and don't seem to have found the answer. I have found a lot of references that beat around the bush but nothing involving an NSArrayController. The controller makes life very easy when binding to other elements of the model entity (such as a master/detail scenario). I have also found a lot of references (although no answers) when using Core Data, but Im not using Core Data.
As per the norm, I'm very grateful for any assistance that can be offered!
Finally figured this one out. Man that took some doing. So here is what I needed to do...
First of all I needed to create an array of my model's key values in my model object and return those key values in an NSDictionary from within the model.
Thus my model got two new methods as follows (based on my simplified example above):
+(NSArray *)personKeys
{
static NSArray * personKeys = nil;
if (personKeys == nil)
personKeys = [[NSArray alloc] initWithObjects:#"firstName", #"lastName", #"gender", #"age", nil];
return personKeys;
}
-(NSDictionary *)personDictionary
{
return [self dictionaryWithValuesForKeys:[[self class] personKeys]];
}
Once implemented, I assign my bound value in my table to
arrayController --> arrangeObjects --> personDictionary.
The last step is to reference the object in the NSCell drawWithFrame and use as needed as follows:
NSDictionary * thisCellObject = [self objectValue];
NSString * objectFirstName = [thisCellObject valueForkey:#"firstName"];
NSString * objectLastName = [thisCellObject valueForKey:#"lastName"];
And as I hoped, any update to the model object reflects in the custom NSCell.
There is another approach that does not require the dictionary. However, it does require the implementation of the - (id)copyWithZone:(NSZone *)zone method within your data class. For example:
- (id)copyWithZone:(NSZone *)zone {
Activity *copy = [[self class] allocWithZone: zone];
copy.activityDate = self.activityDate;
copy.sport = self.sport;
copy.sportIcon = self.sportIcon;
copy.laps = self.laps;
return copy; }
Now in IB, you point the Table Column's value at Array Controller --> arrangedObjects
The drawWithFrame method will now return your actual object from the objectValue.
Activity *rowActivity = [self objectValue];
Changing the class requires updating the copyWithZone method and then accessing the data directly in your drawWithFrame method.
I'm very grateful for this post, Hooligancat, because my project was stalled on this problem, and as hard as I looked at Tim Isted's identical solution at http://www.timisted.net/blog/archive/custom-cells-and-core-data/
I couldn't figure it out.
It was only when I read your excellent simplification of the problem and your version of the solution that the penny dropped - I'm very grateful!