Sort iPhone TableView From RSS Alphabetically - iphone

I am trying to change my podcast view from sorting via pubDate to sorting alphabetically. The code used in parsing the RSS currently for dates is:
NSMutableArray *entries = [NSMutableArray array];
[self parseFeed:doc.rootElement entries:entries];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (RSSEntryDirectory *entry in entries) {
int insertIdx = [_allEntries indexForInsertingObject:entry sortedUsingBlock:^(id a, id b) {
RSSEntryDirectory *entry1 = (RSSEntry *) a;
RSSEntryDirectory *entry2 = (RSSEntry *) b;
return [entry1.articleDate compare:entry2.articleDate];
}];
[_allEntries insertObject:entry atIndex:insertIdx];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]]
withRowAnimation:UITableViewRowAnimationRight];
}
}];
How can I change this to just do alphabetically by one of the other entry properties?

I think you can replace the comparison line:
return [entry1.articleDate compare:entry2.articleDate];
by something like that (assuming it is NSString objects):
return [entry1.articleTitle localizedCaseInsensitiveCompare:entry2.articleTitle];

Related

AFNetworking multiple uploads slowing down main thread

I have a UITabBarController where UITableViewControllerA list files and UITableViewContollerB shows the progress of the files being uploaded.
I have a Singleton class with an upload method that calls my subclass AFHTTPClient and uses NSNotificationCenter to notify my UITableViewControllerB of the upload progress. But this current way is slowing down the UI to where it is almost unusable and I'm not sure how I can improve the process. I read that AFNetworking callback functions are called on the main thread. Is the slow UI response coming from my NSNotificationCenter?
I also would like to mention I'm running this on the Simulator.
Method from my Singleton class.
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:uniqueName forKey:#"unique"];
[dict setObject:[NSNumber numberWithFloat:0] forKey:#"progress"];
[self.active addObject:dict];
[[CustomHTTP client] uploadFileName:#"filename" withBytes:data toPath:serverPath progress:^(float progress) {
[dict setObject:progress forKey:#"progress"];
NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
[info setObject:[NSNumber numberWithInt:[self getIndexByUniquename:uniqueName]] forKey:#"row"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ProgressNotification" object:self userInfo:info];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
} andFailure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
UITableViewControllerB.m
- (void) receiveTestNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"ProgressNotification"]) {
NSDictionary *dict = notification.userInfo;
int row = [[dict objectForKey:#"row"] intValue];
self.inProgress = [Transfer defaultTransfer].active;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
}
You are sending to many notifications. Reloading tableview cells is a somewhat expensive operation. I would restrict the posting of notifications to only when a full percent point has changed or to only one every second.
You can play around with what works best for you, but 8300 notifications is way to much for the tableview to handle.
Instead of calling reloadRowsAtIndexPaths I changed it to find the cell and update the label that way.
for (int i = 0; i < [self.items count]; i++) {
if ([self.items objectAtIndex:i] == transfer) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
TransferCell *cell = (TransferCell *)[self.tableView cellForRowAtIndexPath:indexPath];
cell.percentLabel.text = transfer.completed;
break;
}
}

Insert rows to UITableView crash

Im developing an app, need to load some data in background , then show the data using UITableView.
Here are some codes,
loading data in background:
- (void)loadRelatedItems
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSString *mediaType in allMediaTypes)
{
[self performSelector:#selector(loadRelatedItems:) withObject:mediaType];
}
NSString *notificationName = [CommonFunction allRelatedItemsLoadedNotificationName];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:nil];
[pool release];
}
- (void)loadRelatedItems:(NSString *)mediaType
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSString *keyword in _keywords)
{
NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:#"%#&mediaType=%#&keyword=%#", API, mediaType, keyword]];
NSMutableArray *items = [CommonFunctions arrayFromURL:URL];
if ([items count] == 0) continue;
NSString *notificationName = [CommonFunction partialRelatedItemsLoadedNotificationName];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:items, #"items", mediaType, #"mediaType", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:dic];
}
[pool release];
}
showing the data in UITableView:
- (void)didFinishLoadPartialRelatedItems:(id)sender
{
NSDictionary *dic = [sender userInfo];
NSString *mediaTypeString = [dic objectForKey:#"mediaType"];
NSMutableArray *items = [dic objectForKey:#"items"];
dispatch_async(dispatch_get_main_queue(), ^{
if ([_relatedItems count] == 0)
{
[_relatedItems setObject:items forKey:mediaTypeString];
[_tableView reloadData];
}
else
{
NSMutableArray *mediaTypeItems = [_relatedItems objectForKey:mediaTypeString];
if (mediaTypeItems)
{
// section exist
NSInteger section =[[[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)] indexOfObject:mediaTypeString];
NSMutableArray *indexPaths = [NSMutableArray array];
for (NSMutableDictionary *item in items)
{
[mediaTypeItems addObject:item];
NSInteger newRow = [mediaTypeItems indexOfObject:item];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:newRow inSection:section];
[indexPaths addObject:newIndexPath];
}
[_tableView beginUpdates];
[_tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[_tableView endUpdates];
}
else
{
// new section
[_relatedItems setObject:items forKey:mediaTypeString];
NSInteger section =[[[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)] indexOfObject:mediaTypeString];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[_tableView beginUpdates];
[_tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
[_tableView endUpdates];
}
}
});
}
#pragma mark -
#pragma mark Table Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ([_relatedItems count] == 0) {
return 1;
} else {
return [_relatedItems count];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *allTitles = [[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)];
NSString *title = [allTitles objectAtIndex:section];
NSDictionary *allMediaTypeDisplayNames = [CommonFunction allMediaTypeDisplayNames];
return [allMediaTypeDisplayNames objectForKey:title];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([_relatedItems count] == 0) {
return 0;
}
NSArray *allTitles = [[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)];
NSString *title = [allTitles objectAtIndex:section];
NSInteger rowsCount = [[_relatedItems objectForKey:title] count];
return rowsCount;
}
I'm very confused that it works fine some times, but some times it crashed with message:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1030
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (0) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).
What's the problem? please help.
Please make sure after updating, your number of sections should be equal to number of sections before the update.
As per your code :
the number of sections are defined as like this:
if ([_relatedItems count] == 0) {
return 1;
} else {
return [_relatedItems count];
}
and in this case you are creating new section right ?
else { // new section
[_relatedItems setObject:items forKey:mediaTypeString];
NSInteger section =[[[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)] indexOfObject:mediaTypeString];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[_tableView beginUpdates];
[_tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
[_tableView endUpdates];
}
If you are creating new section then your [_relatedItems count] is increasing. So, please make sure after the inserting also your count should be same.
Right ?
Try This :
if ([_relatedItems count] == 0) {
return 1;
} else {
if([_relatedItems count]>previousCount)
return [_relatedItems count];
return previousCount;
}
when ever you are making any updates to the [_relatedItems]; then change update your previousCount also.. this will be solved
I think your problem is with the number of rows method in the datasource just do one thing define int noOfRows in your .h file .assign your table view array count with the noOfRows. noOfRows=[yourtableArray count];
then return noOfRows from table views numberOfRowsInSection method.
Add 1 to noOfRows if you insert row in the table.
Make noOfRows-- when you delete row from the table
You will not get this exception .Update your array as per your requirement.

How to set Grouped UITableview Section names without a NSFetchedResultsController in play

I have used a number of grouped tables tied to core data managed objects, where the sectionNameKeyPath value is used to identify the attribute in the data that should be used to denote sections for the table.
But how do I indicate the "sectionNameKeyPath equivalent" when I have a table that is being use to present an NSMutableArray full of objects that look like this:
#interface SimGrade : NSObject {
NSNumber * scoreValue;
NSString * commentInfo;
NSString * catName;
}
I would like to have sections defined according to the "catName" member of the class.
Consider, for example that my mutablearray has 5 entries where the 5 "catName" values are "Blue", "Blue", "Blue", "Red", and "Red". So I'd want the number of sections in the table for that example to be 2.
So, what I would 'like to do' could be represented by the following pseudo-code:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return (The number of unique catNames);
}
Note: My interest in doing this is not so much for displaying separate sections in the table, but rather so that I can more easily calculate the sums of scoreValues for each category.
<<<<< UPDATE >>>>>>>>>
Joshua's help, as documented in his response has been right on. Here are the two new handlers for number of sections and number of rows per section...
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSMutableSet *counter = [[NSMutableSet alloc] init];
[tableOfSimGrades enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
[counter addObject:[object catName]];
}];
NSInteger cnt = [counter count];
[counter release];
NSLog(#">>>>> number of sections is -> %d", cnt);
return cnt;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSMutableDictionary *counter = [[NSMutableDictionary alloc] init];
NSMutableArray *cats = [[NSMutableArray alloc] init];
__block NSNumber *countOfElements;
[tableOfSimGrades enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
// check the dictionary for they key, if it's not there we get nil
countOfElements = [counter objectForKey:[object catName]];
if (countOfElements) {
// NSNumbers can't do math, so we use ints.
int curcount = [countOfElements intValue];
curcount++;
[counter setObject:[NSNumber numberWithInt:curcount] forKey:[object catName]];
NSLog(#">>>> adding object %d to dict for cat: %#", curcount, [object catName]);
} else {
[counter setObject:[NSNumber numberWithInt:1] forKey:[object catName]];
[cats addObject:[object catName]];
NSLog(#">>>>> adding initial object to dict for cat: %#", [object catName]);
}
}];
countOfElements = [counter objectForKey:[cats objectAtIndex: section]];
int catcount = [countOfElements intValue];
[counter release];
[cats release];
return catcount;
}
My current issue with this routine now lies in the following function... It is ignorant of any sections in the nsmutableArray and so for each section, it starts at index 0 of the array instead of at the 0th element of the appropriate section.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
SimGrade *tmpGrade = [[SimGrade alloc] init];
tmpGrade = [tableOfSimGrades objectAtIndex: indexPath.row];
cell.detailTextLabel.text = [NSString stringWithFormat:#"Category: %#", tmpGrade.catName];
// [tmpGrade release];
return cell;
}
How do I transform the "indexpath" sent to this routine into the appropriate section of the mutableArray?
Thanks,
Phil
You could do something like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSMutableSet *counter = [[NSMutableSet alloc] init];
[arrayOfSims enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
[counter addObject:object.catName];
}];
NSInteger cnt = [counter count];
[counter release];
return cnt;
}
you'd probably want to memoize that, for performance reasons (but only after profiling it).
--- EDIT ---
You can use an NSMutableDictionary, too, to get counts of individual categories.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSMutableDictionary *counter = [[NSMutableDictionary alloc] init];
__block NSNumber *countOfElements;
[arrayOfSims enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
// check the dictionary for they key, if it's not there we get nil
countOfElements = [counter objectForKey:object.catName];
if (countOfElements) {
// NSNumbers can't do math, so we use ints.
int curcount = [countOfElements intValue];
curcount++;
[counter setObject:[NSNumber numberWithInt:curcount] forKey:object.catName];
} else {
[counter setObject:[NSNumber numberWithInt:1] forKey:object.catName];
}
}];
NSInteger cnt = [counter count];
// we can also get information about each category name, if we choose
[counter release];
return cnt;
}

UITableView / UISearchBar Returns Incorrect Results

I am attempting to implement searching in a UITableView. When searching, it appears that the correct number of results are returned, but I am receiving entries from the original stories array in the results, rather than searchResults. I can see that the searchResults array should be the data source, but haven't been able to figure out after tons of searching quite how to pull it off with an array of NSDictionaries. Any help is appreciated.
- (void)handleSearchForTerm:(NSString *)searchTerm {
[self setSavedSearchTerm:searchTerm];
if ([self searchResults] == nil)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[self setSearchResults:array];
[array release], array = nil;
}
[[self searchResults] removeAllObjects];
if ([[self savedSearchTerm] length] != 0)
{
for (NSDictionary *currentItem in [self stories])
{
if ([[currentItem objectForKey:#"title"] rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentItem];
}
}
}
}
[tableView isEqual:self.searchDisplayController.searchResultsTableView] is also another alternative to making and managing your own BOOL isFiltering; variable
use NSPredicate for filtering
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"self.title MATCHES %#",searchTerm];
Suppose that your original array is "originalArray" so to get the filtered array use this make two more global variables
NSArray* filteredArray;
BOOL isFiltering;
Now in search bar delegate method do following
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"self.title MATCHES %#",searchTerm];
filteredArray = [[originalArray filteredArrayUsingPredicate:predicate] retain];
}
Now you need to change l'll bit your table view delegate and data source, .... for all the places where you are using
NSDictionary *currentString = [originalArray objectAtIndex:indexPath.row];
use following
NSDictionary *currentString;
if(isFiltering)
currentString = [originalArray objectAtIndex:indexPath.row];
else
currentString = [filteredArray objectAtIndex:indexPath.row];

Dealing with editable items in a filtered UITable with an NSMutableArray copy

I have a UITable with a datasource that is set to a 'copy' of the original data. I use this copy to displayed filtered results (either 'All' or only those that are 'checked' with a UISwitch in each row). My logic for doing the filtering is here:
-(void)filterItems {
[tableData removeAllObjects];
if (checkedOrNotSwitch.selectedSegmentIndex == 0) {
[tableData addObjectsFromArray:self.defaultChecklistItemsArray];
} else {
for (NSMutableDictionary *sectionDict in self.defaultChecklistItemsArray) {
NSMutableArray *newItemsArray = [[NSMutableArray alloc] init];
[newItemsArray removeAllObjects];
for (NSMutableDictionary *itemDict in [sectionDict objectForKey:#"categoryItems"]) {
if ([[itemDict objectForKey:#"isComplete"] boolValue]) {
[newItemsArray addObject:itemDict];
}
}
if ([newItemsArray count] > 0) {
NSMutableDictionary *newSectionDict = [[NSMutableDictionary alloc] initWithDictionary:sectionDict];
[newSectionDict setObject:newItemsArray forKey:#"categoryItems"];
[tableData addObject:newSectionDict];
[newSectionDict release];
}
}
}
[checklistTable reloadData];
}
The filtering itself now works correctly. In my custom cell, each row has a UISwitch. The switch runs this function when its changed:
-(IBAction) switchValueChanged{
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
[self.parentController updateCompletedStatusAtIndexPath:indexPath toStatus:isCompleted.on];
}
The function above is in the class for the tableviewcell itself. The function I call in the superview is this:
-(void)updateCompletedStatusAtIndexPath:(NSIndexPath *)indexPath toStatus:(BOOL)status{
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSMutableDictionary *currentsection = [[NSMutableDictionary alloc] initWithDictionary:[tableData objectAtIndex:section]];
NSMutableArray *itemsArray = [[NSMutableArray alloc] initWithArray:[currentsection objectForKey:#"categoryItems"] copyItems:YES];
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] initWithDictionary:[itemsArray objectAtIndex:row]];
NSLog(#"BOOL = %#\n", (status ? #"YES" : #"NO"));
[tempDict setValue:[NSNumber numberWithBool:status] forKey:#"isComplete"];
[itemsArray replaceObjectAtIndex:row withObject:tempDict];
[currentsection setValue:itemsArray forKey:#"categoryItems"];
[tableData replaceObjectAtIndex:section withObject:currentsection];
[tempDict release];
[itemsArray release];
[currentsection release];
[checklistTable reloadData];
}
Before I implemented the filtering and used the logic above on self.defaultChecklistItemsArray directly, it worked and saved the data when the switch was flipped.
Now when I first enter the app, it loads the array of data from nsuserdefaults. I navigate to the view with the table and it displays the data correctly there with the UISwitches all in the correct position given the data (some on, some off). When I tap one of the switches, then click the segmentedcontrol that does the filtering, the data reverts back to the state it was loaded in, implying that the flipping of the switch did not actually effect the data at all (even though I don't think I did a deep copy anywhere here so I figured it should be doing the right thing). Any suggestions?