How to reorder MKMapView annotations array - objective-c

I'd like to reorder the array of annotations shown on a map in order to create a next/prev button for quickly cycling through all annotations (sorted by date) using a simple iterator.
As far as I see the annotations array used as a store [worldmap annotations] is not mutable and therefore I cannot reorder it. I tried the following to create a temporary copy of the annotations array, sort it by date and re-attach it.
(worldmap is my MKMapView object)
//COPY
NSMutableArray *annotationSort = [[NSMutableArray alloc]initWithArray:[worldmap annotations]];
//SORT
[annotationSort sortedArrayUsingComparator:^NSComparisonResult(EventPin* obj1, EventPin* obj2) {
return [obj1.eventItemObject.eventDateBegin compare: obj2.eventItemObject.eventDateBegin];
}];
//ADDED SORTED ARRAY
[worldmap removeAnnotations:[worldmap annotations]];
[worldmap addAnnotations:annotationSort];
This doesn't seem to work. Any idea how can I reorder the MKMapKit annotations array?

As the answer in the linked question mentions, there is no guarantee that the map view's annotations property will preserve any order.
In addition, since the annotations property includes the userLocation if you have showsUserLocation turned on (but which you don't yourself explicitly call addAnnotation for), the annotation order will not be what you may expect.
Don't rely on the order of the annotations in the map view's annotations array.
Instead, keep your own array of references to the annotations and sort them any way you want (like your annotationSort array).
But there's no point in removing them from the map and adding them back.
Keep in mind that the map view's annotations array may contain the MKUserLocation annotation so when constructing your array, check the type of each annotation before including it or accessing custom properties.
However, note that the code to sort the array:
[annotationSort sortedArrayUsingComparator:...
is flawed itself because sortedArrayUsingComparator returns an NSArray.
It does not sort the array in-place.
Instead, to sort an NSMutableArray, call its sortUsingComparator method.
Using this sorted array, your app can access or select the annotations in the order desired.

Related

Filling NSMutableArray for later use in obj-c

How do you fill a NSMutableArray with a set capacity for later use?
Basically I want to set up a NSMutableArray to act as a map for my game objects, so I have this line...
gameObjects = [[NSMutableArray alloc] initWithCapacity:mapWidth*mapHeight];
Which I had hoped would create and fill my MutableArray so I can get then access it with this kind of index...
int ii = (cellY*mapWidth)+cellX;
NSDictionary *currentObject = [gameObjects objectAtIndex:ii];
But I just learned initWithCapacity doesn't fill the array, so should I create blank objects to fill it with, or is there a Null that I can fill it with? Also would I do that with 2 for loops or is there an instruction something like "initWith:myObject" ?
I want to be able to check at a certain index within the array to see if there's an object there or not, so I need to be able to acces that index point, and I can only do that if there's something there or I get an out of bounds error.
I'll be using this NSMutableArray pretty much as a grid of objects, it's a 1 dimensional array organised as a 2 dimensional array, so I need to be able to fill it with mapWidth*mapHeight of something, and then calculate the index and do a check on that index within the array.
I've looked on here and googled but couldn't find anything like what I'm asking.
Thanks for any advice.
I think what you are looking for is [NSNull null]. It is exactly what you want- a placeholder value.
You can find more information on the topic in this question.
initWithCapacity is just a performance optimization -- it has no effect on the array behavior, it just keeps the code "under the covers" from having to repeatedly enlarge the internal array as you add more entries.
So if you want a "pre-allocated" array, you'd need to fill it with NSNull objects or some such. You can then use isKindOfClass to tell if the object is the right type, or simply == compare the entry to [NSNull null]. (Since there's only ever one NSNull object it always has the same address).
(Or you could use a C-style array of pointers with nil values for empty slots.)
But you might be better off using an NSMutableDictionary instead -- no need to pre-fill, and if the element isn't there you get a nil pointer back. For keys use a NSNumber object that corresponds to what would have been your array index.
initWithCapacity only hints to NSMutableArray that it should support this many objects. It won't actually have any objects in it until you add them. Besides, every entry in the array is a pointer to an object, not a struct like you'd normally have in a standard c array.
You need to change how you're thinking about the problem. If you don't add an object to the array, it's not in there. So either you pre-fill the array with "empty" objects as you've said, which is weird. Or you can add the objects as you need them.

Best way to add/remove to an NSMutableArray managed by an NSArrayController

I've got a NSMutableArray (containing NSMutableDictionary instances) bound to an NSArrayController (the NSArrayController is in turn bound to NSTableView columns).
What is the most Cocoa-, and KVO- friendly way of, programmatically :
adding a new empty object (NSMutableDictionary) to the array?
removing currently selected object? (after removing, the previous item - if exists - should be selected)
I've always been doing this in a way I don't particularly like - and I'm sure it's not the best way around (too many lines of code for something so simple : in Cocoa that indicates a wrong take on the subject :-)).
My code (quite an overkill, actually) :
Adding to the Array
NSMutableArray* oldParams = [paramsArray mutableCopy];
[oldParams addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:#"Parameter",#"Parameter",#"",#"Value", nil]];
[self setParamsArray:oldParams];
[paramsController setSelectionIndex:[paramsArray count]-1];
Removing currently selected object from the Array
if ([paramsArray count]>0)
{
int s = [paramsController selectionIndex];
NSMutableArray* oldParams = [paramsArray mutableCopy];
[oldParams removeObjectAtIndex:s];
[self setParamsArray:oldParams];
if (s<=[paramsArray count]-1)
[paramsController setSelectionIndex:s];
else
[paramsController setSelectionIndex:[paramsArray count]-1];
}
So, what are your opinions on that?
Given that the array controller is bound to a property named paramsArray on some object, the best approach is to define the key-value coding indexed accessors on that object's class. Then, use those accessors to mutate the to-many relationship represented by the property in a KVO-compliant manner.
For example, implement -insertObject:inParamsArrayAtIndex: and then use that to add an object. If you like the convenience of NSMutableArray's -addObject: method, you can write an -addObjectToParamArray method that forwards to -insertObject:inParamsArrayAtIndex:.
By the way, "paramsArray" is a poor name for a property. The property name shouldn't encode the type used to implement it. If you look at the templates for the indexed accessor names, you'll see that Apple is expecting to-many relationship properties to just be a plural noun like "params" (no "Array"). For example, -paramsAtIndexes: is better than -paramsArrayAtIndexes:.
You have to think of your array as the controller's backing store, and that it's managing it for you.
Adding an object:
[[self accountsArrayController] addObject:accountDictionary];
Removing the currently selected object:
[[self accountsArrayController] remove:nil];
You'll have to write another line or two to make that previous item selected, but that's an exercise I leave to you.

How to show calculated values in NSTableView?

I have an NSTableView that binds via an NSArrayController to an NSMutableArray of NSString's.
There's also a TextView in the same window. What I want to do is display a number of occurrences of each NSString in the NSTableView.
I've thought of a way to do this, but that doesn't look elegant way of doing this:
Inherit from NSString and define new method that performs a search in predefined object (NSTextView) and returns the number of occurrences.
I'm guessing there must be a more natural way of achieving the same result?
EDIT:
Sorry, should have clarified. NSSMutableArray is an array of NSObjects that have an NSString property. I suppose I could define an extra method (findAllOccurencesOfString:inString:) which would return a number. But the question is how do I bind to this function and in that binding how to I pass a var (pointer to textField)??
You'll need to have a wordCount (read only) property on whatever objects are in your table data source, this will have to call your new method internally using the object's own string value, as you can't pass parameters in bindings (unless they've changed, I haven't used bindings for a while as I've been concentrating on iOS). Then bind to this property for the column in the table. Presumably you don't need to pass the pointer to the textfield as there is only one?

"NSSet allObjects" does random ordering?

I have the following code:
self.temporaryImageArray = [(NSSet *)[[array objectAtIndex:0] images] allObjects]
Array holds an Band object from my CoreData model. It has an NSSet as a property called "images".
Now I use this temporaryImageArray to determine via timestamps whether or not the images need to be updated. I have come across some very random behavior and my question now is:
Does [NSSet allObjects] return the Objects from the Set randomly in no order?
Is there any way to prevent this or to have it return it in order? It would lessen the complexity of my code a lot.
NSSet is an unordered collection. It has no idea what the "order" of its objects are. Therefore, when you call -allObjects, it returns them unordered.
Note that the documentation on -allObjects states:
An array containing the set’s members, or an empty array if the set has no members. The order of the objects in the array isn’t defined.
(emphasis mine)
A set does not have order. However, in 10.7 (Lion), there is an NSOrderedSet class. This isn't available in iOS 4.0.
NSOrderedSet and NSMutableOrderedSet are now available in iOS 5 just so you're kept up to date.
Here's the link

Populating NSTableview from a mutable array

I've been attempting this for two days, and constantly running into dead ends.
I've been through Aaron Hillegass's Cocoa Programming for MAC OS X, and done all the relevant exercises dealing with NSTableview and mutable arrays, and I have been attempting to modify them to suit my needs.
However none of them seem to be using an array with objects as a data source, it seems to use the tableview as the datasource.
I'm trying to implement Jonas Jongejan's "reworking" of my code here, with a Cocoa front end to display the results.
Any pointers or suggestions I know this should be simple, but I'm lost in the wilderness here.
I can populate the table by setting the array
It's pretty simple really, once you get to understand it (of course!). You can't use an NSArray directly as a table source. You need to either create a custom object that implements NSTableViewDataSource or implement that protocol in some existing class - usually a controller. If you use Xcode to create a standard document based application, the document controller class - (it will be called MyDocument) is a good class to use.
You need to implement at least these two methods:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you have a mutable array whose values you'd like to use in a table view with one column, something like the following should do as a start:
– numberOfRowsInTableView: (NSTableView*) aTableView
{
return [myMutableArray count];
}
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
return [myMutableArray objectAtIndex: rowIndex];
}
It has just occurred to me that you could add the above two methods as a category to NSArray replacing myMutableArray with self and then you can use an array as a data source.
Anyway, with a mutable array, it is important that any time you change it, you need to let the table view know it has been changed, so you need to send the table view -reloadData.
If your table view has more than one column and you want to populate it with properties of objects in your array, there's a trick you can do to make it easier for yourself. Let's say the objects in your array are instances of a class called Person with two methods defined:
-(NSString*) givenName;
-(NSString*) familyName;
and you want your table view to have a column for each of those, you can set the identifier property of each column to the name of the property in Person that that column displays and use something like the following:
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
Person* item = [myMutableArray objectAtIndex: rowIndex];
return [item valueForKey: [tableColumn identifier]];
}
If you replace valueForKey: with valueForKeyPath: and your Person class also has the following methods:
-(Person*) mother;
-(Person*) father;
-(NSString*) fullName; // concatenation of given name and family name
you can add table columns with identifiers like: father.fullName or mother.familyName and the values will be automatically populated.
You could go the datasource route and do all of the heavy lifting yourself, or you could let bindings do all the heavy lifting for you. Add an NSArrayController to the nib file that has the table view in it. Make sure that the File's Owner of the nib is set to the same class that has the mutable array in it. Bind the contentArray of the array controller to File's Owner.myMutableArray. For each column bind Value to the array controller arrangedObjects and add the appropriate key path. This will allow you to get things like user sorting for free if you ever need it.
On the iPhone (I know you're talking about Mac, but maybe this could help) you have to use delegation for loading a tableView. It asks for a cell and you use your array to fill-in the data where needed.
I'm not sure if this works for the Mac, but it'd be worth looking into.
Maybe set dataSource to self and use those delegate methods to access your array based on the row and column #
Apple has a whole guide for Table View Programming so I suggest you start with the Using a Table Data Source section of the that guide.