I'm reading an example in More Iphone Development book in the Core Data section, and the author creates a category to turn the NSIndexPath into its row key and row label. Here's the code:
#implementation NSArray(NestedArrays)
- (id)nestedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger section = [indexPath section];
NSArray *subArray = [self objectAtIndex:section];
if (![subArray isKindOfClass:[NSArray class]])
return nil;
if (row >= [subArray count])
return nil;
return [subArray objectAtIndex:row];
}
- (NSInteger)countOfNestedArray:(NSUInteger)section {
NSArray *subArray = [self objeectAtIndex:section];
return [subArray count];
}
#end
In the first method, after he gets the row and section for the NSIndexPath, I'm not sure what's going on afterwards. I don't see why he creates a new array at the section, and then I don't understand the reason behind the two if statements that follow. Thanks in advance.
The code does not actually create a new array, the author is simply holding a reference to an object that is already stored in self. Annotations for each line are below.
Get the row from the indexPath:
NSUInteger row = [indexPath row];
Get the section index from the indexPath:
NSUInteger section = [indexPath section];
Dereference the array within self at index section
NSArray *subArray = [self objectAtIndex:section];
If the sub array is not really an array, fail:
if (![subArray isKindOfClass:[NSArray class]])
return nil;
If the row within the sub array is greater than its last index, fail:
if (row >= [subArray count])
return nil;
Return the object in the sub array at the given row:
return [subArray objectAtIndex:row];
Maybe it would help if some comments were included:
// Get the subarray corresponding to the requested section
NSArray *subArray = [self objectAtIndex:section];
// verify that it is really an NSArray
if (![subArray isKindOfClass:[NSArray class]])
return nil; // not an array - fail
// Check the array subscript (row #) against the size of the subarray
if (row >= [subArray count])
return nil; // out of bounds - fail
return [subArray objectAtIndex:row]; // return the requested element
He doesn't create a new array. He get's the "sub" array that represents the section.
The first if statements is there because you can create a NSArray that (let's say) is full of NSStrings. And if the subItem would be a NSString, objectAtIndex: would raise an exception.
And the second one protects you when you try access an item that is on a index that is not in the subArray. If you wouldn't return nil at this position there would be an exception too.
First, he is not creating a new array. He is just taking object for specified indexPath and checks weather it is array. If it is not array, or specified row is larger then array's size he returns nil.
He gets the row and the section, and then he gets the array specified by that section. Then he does two sanity checks:
Is thing I've got actually an array?
Is the row number valid for this particular array?
If both of these are true, he returns the object from the section array at the index specified by row.
Related
I have an array of items, each with their own unique descriptions. Basically, I want to create a method which takes each item from the array and returns a single descriptive string which shows the description of each item in said array.
- (NSString *) itemList
{
NSString *list = [[NSString alloc] init];
for (Item *i in _items)
{
/**
Unsure :S
*/
[NSString stringWithFormat:#"%#: %#.\n", [i firstId], [i name]];
}
return list;
}
Basically, this is the coded logic that I have so far.
Assume I have two items which are initialised as such:
Item *testItem1 = [[Item alloc] initWithIdentifiers:#[#"shovel", #"spade"] name:#"a shovel" andDesc:#"This is a mighty fine shovel"];
Item *testItem2 = [[Item alloc] initWithIdentifiers:#[#"gem", #"crystal"] name:#"a gem" andDesc:#"This is a shiny gem"];
I then add those items to my Inventory object:
[testInventory put:testItem1];
[testInventory put:testItem2];
By calling the Inventory method itemList
[testInventory itemList];
on my inventory (code listed above), I want the following result:
#"shovel: a shovel.\ngem a gem."
Does anyone have any suggestions or pointers. I'm sure it's simple; it's just that I've only recently picked up Obj - C :)
Thanks
You can just use:
list = [list stringByAppendingFormat:#"%#: %#\n", [i firstId], [i name]];
or try NSMutableString:
NSMutableString *list = [[NSMutableString alloc] init];
[list appendFormat:#"%#: %#\n", [i firstId], [i name]];
You can do it more elegantly by overriding the description method for your Item class like this:
- (NSString *) description {
return [NSString stringWithFormat:"%#: %#.", [self firstId], [self name]];
}
and then to generate the string for all the items in the array:
NSString* itemsString = [itemList componentsJoinedByString:#"\n"];
I like adding the collection of strings to a mutable array and then calling componentsJoinedByString. It works even more cleanly if it is the description method you want on each object because you don't have to do the collecting loop first.
Create nsmutablearray
For each item in list
Nsmutablearray add object item.property
Return nsmutablearray componentsJoinedByString #", "
If you want the item's description though, you can just do, assuming you have an array with the objects already
TheArray componentsJoinedByString #", "
Can't handle simple problem - adding cells to UITableView.
I have single-view application, with added from Objects - Table View and simple NSArray (deseriliazed json from internet-grabbed data).
- (void) didLoadMusicList:(APIDownload *)request
{
NSLog(#"Music list loaded");
CJSONDeserializer *deserializer = [CJSONDeserializer new];
NSDictionary *dict = [deserializer deserializeAsDictionary:request.downloadData error:nil];
NSArray *response = [dict objectForKey:#"response"];
NSArray *audios = [response subarrayWithRange:NSMakeRange(1, response.count-1)];
for(int i = 0; i < audios.count; i++)
{
NSDictionary *audio = [audios objectAtIndex:i];
// add a cell?
}
}
So, how do I add cell for each element?
You need to implement the UITableViewDatasource on your view controller. You can use the same array to provide the data to the cells, and return them using the cellForRowAtIndexPath datasource method. You also need to provide with the count of cells on your tableView.
Check this documentation: http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html
Currently I am coding the search capability of a UISearchBar using data from a DB that's displayed in a table. I figure out the search results and save them to NSMutableArray *searchResults. Here's how that looks:
- (void) searchGrapesTableView {
[searchResult removeAllObjects];
numSearchWines=0;
for (NSString *str in listOfWines)
{
NSRange titleResultsRange = [str rangeOfString:searchBar.text options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0) {
[searchResult addObject:str];
numSearchWines++;
}
}
}
listOfWines is also an NSMutableArray that contains a list of all the names that can be searched from. IDEALLY, I would like to determine the index of the object in listOfWines that has the matching value and add it to another NSMutableArray, so that way I can later access it really easily. For example, later on when I display the table cells using this search data, I have the following code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"wineCell"];
//determine how many to add to get the grape ID
NSInteger grapeID=0;
for (int i=0; i<indexPath.section; i++) {
grapeID += [self.tableView numberOfRowsInSection:i];
}
Wines *currentWine;
if (isSearchOn) {
NSString *cellValue = [searchResult objectAtIndex:(grapeID+indexPath.row)];
NSInteger index = 0;
index = [listOfWines indexOfObject:cellValue];
currentWine = [allWines objectAtIndex:(index)];
}
else {
currentWine = [allWines objectAtIndex:(grapeID+indexPath.row)];
}
cell.textLabel.text = currentWine.name;
return cell;
}
Unfortunately this doesn't work if there are duplicate entries in listOfWines (and subsequently searchResult). That's why it would be so useful to also have their indexes in, for example, an NSMutableArray named searchResultID, so that I can do something like this:
NSInteger theResultID = [searchResultID objectAtIndex:(grapeID+indexPath.row)];
currentWine = [allWines objectAtIndex:theResultID];
This sounds like a job for indexesOfObjectsPassingTest:. You should check it out in the NSArray docs. You use it something like this:
NSIndexSet *indxs = [allWines indexesOfObjectsPassingTest: ^BOOL(NSString *obj, NSUInteger idx, BOOL *stop) {
return [obj isEqualToString:searchBar.text]];
}];
So this will loop through all the values in allWines (I'm assuming the objects are strings) and return the indexes of any that match the search string, giving you an index set with any indexes found.
I am trying to implement a method to clear the NSTableView of all items AND columns. But I get a crash when I try to implement the following:
- (void)clearResultData
{
[resultArray removeAllObjects];
NSArray *tableCols = [resultTableView tableColumns];
if ([tableCols count] > 0)
{
id object;
NSEnumerator *e = [tableCols objectEnumerator];
while (object = [e nextObject])
{
NSTableColumn *col = (NSTableColumn*)object;
[resultTableView removeTableColumn:col];
}
}
[resultTableView reloadData];
}
Well, if it's any help you can remove all the columns like this:
- (void)removeAllColumns
{
while([[tableView tableColumns] count] > 0) {
[tableView removeTableColumn:[[tableView tableColumns] lastObject]];
}
}
The NSArray returned by tableColumns is changed by removeTableColumn. Do not assume it is unchanged.
Although it is returned as a non-mutable NSArray, the underlying implementation is being modified and it is not safe to use NSEnumerator with collections that are modified. In the while loop, you are sending a nextObject message to an enumerator whose current object was just deleted -- so bad things can happen!
Here's a more efficient implementation:
NSTableColumn* col;
while ((col = [[tableView tableColumns] lastObject])) {
[tableView removeTableColumn:col];
}
When there are no columns in the table view: tableColumns returns an empty array, lastObject on an empty array returns nil, col is assigned the value of nil, the condition is false and the while loop finishes.
[[[_tableView tableColumns] copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[_tableView removeTableColumn:obj];
}];
Here is a Swift implementation:
tableView.tableColumns.forEach({tableView.removeTableColumn($0)})
I'm having a hard time scraping together enough snippets of knowledge to implement an NSOutlineView with a static, never-changing structure defined in an NSArray. This link has been great, but it's not helping me grasp submenus. I'm thinking they're just nested NSArrays, but I have no clear idea.
Let's say we have an NSArray inside an NSArray, defined as
NSArray *subarray = [[NSArray alloc] initWithObjects:#"2.1", #"2.2", #"2.3", #"2.4", #"2.5", nil];
NSArray *ovStructure = [[NSArray alloc] initWithObjects:#"1", subarray, #"3", nil];
The text is defined in outlineView:objectValueForTableColumn:byItem:.
- (id)outlineView:(NSOutlineView *)ov objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)ovItem
{
if ([[[tableColumn headerCell] stringValue] compare:#"Key"] == NSOrderedSame)
{
// Return the key for this item. First, get the parent array or dictionary.
// If the parent is nil, then that must be root, so we'll get the root
// dictionary.
id parentObject = [ov parentForItem:ovItem] ? [ov parentForItem:ovItem] : ovStructure;
if ([parentObject isKindOfClass:[NSArray class]])
{
// Arrays don't have keys (usually), so we have to use a name
// based on the index of the object.
NSLog([NSString stringWithFormat:#"%#", ovItem]);
//return [NSString stringWithFormat:#"Item %d", [parentObject indexOfObject:ovItem]];
return (NSString *) [ovStructure objectAtIndex:[ovStructure indexOfObject:ovItem]];
}
}
else
{
// Return the value for the key. If this is a string, just return that.
if ([ovItem isKindOfClass:[NSString class]])
{
return ovItem;
}
else if ([ovItem isKindOfClass:[NSDictionary class]])
{
return [NSString stringWithFormat:#"%d items", [ovItem count]];
}
else if ([ovItem isKindOfClass:[NSArray class]])
{
return [NSString stringWithFormat:#"%d items", [ovItem count]];
}
}
return nil;
}
The result is '1', '(' (expandable), and '3'. NSLog shows the array starting with '(', hence the second item. Expanding it causes a crash due to going 'beyond bounds.' I tried using parentForItem: but couldn't figure out what to compare the result to.
What am I missing?
The example behind the link you included shows an NSDictionary taking care of the subarray stuff, if I'm reading it correctly. So I think your ovStructure should not be an array but a dictionary. But, more fundamentally, I think you should really look into NSTreeController. Unfortunately, NSTreeController is notoriously hard to work with, but improvements were made last year and even I got it working in the end. Good luck.