Tuesday, December 29, 2009

SuperDB Core Data App with Sections

If you've gotten through the first few chapters of More iPhone 3 Development, you might be wondering why we included a sectionNameKeyPath when we didn't actually divide up the table into sections. What's the point of having the fetched results controller use sections if they're not displayed?

The truth of the matter is that we originally planned to take the SuperDB application further than we were able to. Unfortunately, we reached a point where we had to cut it off and move on to other topics in order to both meet our deadline and to come in at a reasonable page count (as it was, we came in 250 pages over what we contracted for). Okay, we didn't actually meet our deadline, but we would have missed it by more.

Dave and I agreed to stop working on Core Data to get the book done and leave room for the other topics, but we left open the possibility of expanding the application further here in my blog. In order to be able to do that, we left in some vestiges of the original plan to make it easier to expand the application here.

Here's the first expansion, which is to add alphabetical sections to the table, like so:

Screen shot 2009-12-29 at 5.14.44 PM.png


Let's continue on from the code in the 07 - SuperDB project. You can download the revised version from here. Make a new copy of it if you wish. The first thing we need to do is add a tableview delegate method that returns the title to be displayed in each section. To do that, add the following code to HeroListViewController.m, near the other table view methods:

- (NSString *)tableView:(UITableView *)tableView 
titleForHeaderInSection:(NSInteger)section {

if (!(section == 0 && [self.tableView numberOfSections] == 1)) {
id <NSFetchedResultsSectionInfo> sectionInfo =
[[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo name];
}

return nil;
}

This is a very generic method that will use the values from the fetched results controller. This method becomes basically a copy-and-paste bit of code that you can use unchanged in any controller that uses a fetched results controller with sections.

If you run your application now, however, you're going to get a separate section for each row (it would also crash, but we'll deal with that as well). In the version you've got now, we specified either name or secretIdentity as our sectionNameKeyPath, so every unique name or secret identity becomes its own section. Generally, not what want. So, the next step is to add virtual accessor method to our Hero object to return the value that we want to use to use to divide the list of heroes up. Let's do it alphabetically, so that means we need to create methods to return the first letter of the name and the secret identity. We can then use these new virtual accessor methods as our section name key paths, and the fetched results controller will divvy up the list by first letter.

In Hero.h, add the following two method declarations, just before the @end keyword:

- (NSString *)nameFirstLetter;
- (NSString *)secretIdentityFirstLetter;

Save Hero.h and switch over to Hero.m, and insert the implementation of these two methods, right above the @end keyword again:

- (NSString *)nameFirstLetter {
return [self.name substringToIndex:1];
}

- (NSString *)secretIdentityFirstLetter {
return [self.secretIdentity substringToIndex:1];
}

Save Hero.m. Next, we have to make a few changes in HeroListViewController.m. First, change the assignment of sectionKey to reflect our new virtual accessor methods. Look for the following code in the fetchedResultsController method and change the lines in bold to switch from using name and secretIdentity to using our new first-letter methods:

...
case kByName: {
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"secretIdentity" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
sectionKey = @"nameFirstLetter";
break;
}

case kBySecretIdentity:{
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"secretIdentity" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
sectionKey = @"secretIdentityFirstLetter";
break;
}

...


That's basically it. Well, no really. In testing this, I found that it crashed. For this new version, we need to tweak the fetched results controller delegate methods that we gave you in Chapter 2. Replace your existing implementations of controller:didChangeSection:atIndex:forChangeType: and controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: with the following new versions:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}

sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath] isEqual:currentKeyValue])
break;

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}

}

if (newSectionLocation == -1)
return; // uh oh

if (!((newSectionLocation == 0) && (tableSectionCount == 1) && ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation] withRowAnimation:UITableViewRowAnimationFade];
NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices length:2] autorelease];
}

}

case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount >= tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
else
if (tableSectionCount > 1)
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];


[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight
]
;

}

else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}

break;
default:
break;
}

}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {

case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) ))
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}

}

Since our original application gave every row its own section, we didn't have this problem, but what happens is when we receive notice that a row has moved, we need to make sure that the number of sections in the table and controller match. It's possible that a row moved to a new section, possibly even causing a row to be deleted or inserted in the controller, but not the table. If a row is moved from one section to another, and we don't account for it, the app will crash. If the moved row had been the last row in a section, we need to delete the section from the table, unless it was the last section, because tables have to have at least one section. If moving the row created a new section in the controller, we insert a new row into the table.

And that's really it, you now have your SuperDB's hero list divide up by first letter of either the hero's name or secret identity, depending on which tab was selected.

You should use the fetched results controller delegate methods from this posting instead of the ones in the book. The ones in the book work just fine for the application's in the book, but this one is more flexible and will handle a greater variety of situations correctly.



24 comments:

Cameron Knight said...

I found a bug?

I added a superhero, enter the name "Super Man", Identity "Clark Kent", Date "Feb 29, 1936", and it crashes.

Here is the console message:

2009-12-30 14:58:17.472 SuperDB[2632:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-984.38/UITableView.m:772
2009-12-30 14:58:17.473 SuperDB[2632:207] Serious application error. Exception was caught during Core Data change processing: Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted). with userInfo (null)
2009-12-30 14:58:17.474 SuperDB[2632:207] *** 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 (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).'
2009-12-30 14:58:17.478 SuperDB[2632:207] Stack: (
30970971,
2438268169,
31054907,
859924,
3158918,
3104598,
16215,
29731809,
177722,
30703621,
167664,
29077885,
29468515,
28964330,
29185832,
29576,
32943,
2802777,
4716533,
2802777,
3210146,
3218883,
3214095,
2907699,
2816028,
2842805,
39067345,
30755712,
30751816,
39061389,
39061586,
2846723,
9132,
8986
)

Cameron Knight said...

Never-mind, user error :)

Btw, really liking the new book.

Jeff LaMarche said...

Cameron:

Glad it was user error - that's the same bug I spent so much time trying to work around. Would have been really bummed if my fix hasn't really worked...

Thanks!
Jeff

Yvonne said...

Hmm, I'm not sure what user error Cameron found, but I'm also having the save issue.

After double checking my files, I tried building the project from the "SuperDB - Sections" download and encountered the same thing (adding Superman, Clark Kent, and some date in 1936) The crash occurs when the save button is pressed for the date. I looked through the ManagedObjectDateEditor files pretty carefully, and I'm just not seeing it there.

As a followup, I tried it again, skipping over the birthdate and trying to put in the Sex first. Same result - crash when trying to save with the ManagedObjectSingleSelectionListEditor.

Scouring ManagedObjectEditor now, with nothing jumping out at me (but then, I'm a relative newbie).

Console:

[Session started at 2010-01-01 13:06:44 -0500.]
2010-01-01 13:07:24.566 SuperDB[36876:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-984.38/UITableView.m:772
2010-01-01 13:07:24.567 SuperDB[36876:207] Serious application error. Exception was caught during Core Data change processing: Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted). with userInfo (null)
2010-01-01 13:07:24.568 SuperDB[36876:207] *** 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 (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).'
2010-01-01 13:07:24.569 SuperDB[36876:207] Stack: (
30970971,
2496869641,
31054907,
859924,
3158918,
3104598,
16215,
29731809,
177722,
30703621,
167664,
29077885,
29468515,
28964330,
29185832,
29576,
32943,
2802777,
4716533,
2802777,
3210146,
3218883,
3214095,
2907699,
2816028,
2842805,
39067345,
30755712,
30751816,
39061389,
39061586,
2846723,
9132,
8986
)

digitalIdea said...

I'm facing the same bug described by Cameron and Yvonne

eric said...

I am getting the exact same crash on saving the birthday as well - device or simulator with 3.1.2. Also, if I try to add a second superhero, I get the crash below. This is all on unmodified code - straight from the download link. Any ideas? (To be fair, I haven't done any debugging yet)

2010-01-03 16:19:40.630 SuperDB[32110:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-984.38/UITableView.m:772
2010-01-03 16:19:40.631 SuperDB[32110:207] Serious application error. Exception was caught during Core Data change processing: Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)
2010-01-03 16:19:40.632 SuperDB[32110:207] *** 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 (2) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
2010-01-03 16:19:40.633

Yvonne said...

I haven't had time to experiment yet, though it seems likely that the bug is contained within

case NSFetchedResultsChangeUpdate:

Yvonne said...

More specifically, how the code changed relate to the existing

case NSFetchedResultsChangeUpdate:

eric said...

I think the problem MAY relate to these lines of code:
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath] isEqual:currentKeyValue])
break;

since the sectionKeyPath we use is not a defined persistent property (From the documentation for committedValuesForKeys: "This method only reports values of properties that are defined as persistent properties of the receiver, not values of transient properties or of custom instance variables."), it will not be returned in the call -- therefore we never hit our break statement as intended.

I'm sure this could be fixed somehow, however, I think the code in the NSFetchedResultsChangeUpdate is not needed. The point of this code is to check if a property that will affect the sorting or sectionKeyPath properties has changed (and therefore, we must do an update to the tableView), however, this situation will never happen - type will only be set to NSFetchedResultsChangeUpdate if the changed attributes don't affect the sort descriptors, if they do, type will be NSFetchedResultsChangeUpdate*. (I am making the assumption that the sectionKeyPath is the sort descriptor, which isn't quite true here, but still holds true because we are using the first letter of the sort descriptor - same difference).

Commenting out the code and adding break; to NSFetchedResultsChangeUpdate fixes the crash upon changing the birthday. I added in a refresh of the entire section because the project I am using this code in has multiple variables displaying in the tableview - I want the view refreshed if these change.

We just need to worry inserting/removing sections in the NSFetchedResultsChangeMove. I made some changes to the logic because I kept getting crashes when the last row in one section was changes such that another section was created. I think the code below is self-explanatory, but if not, let me know. (I place bolded lines that were changed - code wouldn't fit in this comment, see next comment)

*from the NSFetchedResultsController header file:
"The Move object is reported when the changed attribute on the object is one of the sort descriptors used in the fetch request. An update of the object is assumed in this case, but no separate update message is sent to the delegate.
The Update object is reported when an object's state changes, and the changed attributes aren't part of the sort keys."

eric said...

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
//added line to refresh the table view - could be made to only refresh if a displayed property changes
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
//all other code removed

break;
case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger fetchedResultControllerSectionCount = [[controller sections] count];
if (fetchedResultControllerSectionCount > tableSectionCount) //section added (removed equal sign)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
else if (fetchedResultControllerSectionCount < tableSectionCount) { //section went away (added condition)
if (tableSectionCount > 1) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];
}
} else { //moved last row in a section to a NEW section ** (added this conditional logic)
//we could potentially add a check to ensure newIndexPath.section != indexPath.section
//but i can't think of a scenario that would make that happen
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];
}


[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationRight];

[self.tableView reloadSectionIndexTitles]; //added line - index titles were not updating (doesn't apply to this example, but i have section index titles in my other project)
}
else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}
break;
default:
break;
}
}

Jeff LaMarche said...

Eric:

The code in NSFetchedResultsChangeUpdate is most definitely needed. Inserting a break is also a very bad idea.

I will look at this more when I get a chance.

For the record, I really hate NSFetchedResultsController.

Jeff LaMarche said...

Try re-downloading the source. I had a >= when I should have had a >, and it was causing sections to be inserted when it wasn't needed.

Probably won't be the last change needed - NSFetchedResultsController is a beast and this code is gnarly and complex. Unfortunately, I don't get a tech reviewer for my blog posts :(

eric said...

Why do you need the code in the NSFetchedResultsChangeUpdate section? I may be missing something, but type will never be set to NSFetchedResultsChangeUpdate if the change affects the sort keys (the section keys will only change when the sort keys change) - instead, it will be set to NSFetchedResultsChangeMove.

Also, you need to add a third condition in NSFetchedResultsChangeMove, for when frcSectionCount = tableSectionCount with the following:

[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];


This prevents a crash in the scenario of changing the only row in a section such that a new section is created. To see this, make three names Alan, Eric, Tony. Now change the name Alan to Zeta and you will get a crash (shown below). This crash won't happen if you add the above condition.

2010-01-05 16:26:55.547 SuperDB[44326:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-984.38/UITableView.m:774
2010-01-05 16:26:55.548 SuperDB[44326:207] Serious application error. Exception was caught during Core Data change processing: Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted). with userInfo (null)
2010-01-05 16:26:55.548 SuperDB[44326:207] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'

Godar said...

sneakers shoes
Nike Tn
he was interrupted,
discount nike shoes
nike shox r4
tn dollar
He replied, "Yes......you are right! I haven't been looking for anyone for the past years."
With that, the man darted across the floor and out the door,
cheap nike shoes
All along he has found his girl.
nike tennis shoes That was why he did not bother to look further when he realized she was not coming back. It was not any specific girl he was seeking!
cheap nike shoxIt was perfection that he wanted, and yes.....perfection!!
Relationship is something both parties should work on. Realizing that he had let away someone so important in his life, he decided to call her immediately. His whole mind was flooded with fear.
free shipping shoes He was afraid that she might have found someone new or no longer had the same feelings anymore..... For once,
Paypal Credit card Accepthe felt the fear of losing someone.
nike shoes
nike discount shoes
,cheap puma shoes
nike shox shoes
chaussures nikeHow he damned the Gods...!!
nike free shoes How he hated himself....for taking so long to realize his mistake!! That was in 1996.
buy shoes onlineYou may painfully regret, only to realise that it is too late.

Manu Anand said...

Am also having the same issue

Nick DeDomenico said...

I am having the same problem with the code downloaded right off the site without putting in any of the section logic. I tried to see if it was working better than the source I downloaded for the book. After I insert one super hero, upon trying to insert another one I get a crash.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

soch said...

Any updates to this blog? Was the beast tackled?

Andrew said...

I was able to fix this bug by using:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[fetchedResultsController sections] count];
}


Instead of:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// DO NOT USE THIS CODE, IT WILL CAUSE CRASHES!
NSUInteger count = [[fetchedResultsController sections] count];

if (count == 0) {
count = 1;
}

return count;
}

SEO Services Consultants said...

Nice information, many thanks to the author. It is incomprehensible to me now, but in general, the usefulness and significance is overwhelming. Thanks again and good luck! Web Design Company

mewmewmew said...

If you've gotten through the first few chapters of More iPhone 3 Development, you might be wondering why we included a sectionNameKeyPath when we didn't actually divide up the table into sections. What's the point of having the fetched results controller use sections if they're not displayed?




auto transport quotes
tower defense

mewmewmew said...

Yep, sure did. The previous sections sets everything up to use the logic from that section. Rather than create a method or function to hold the common logic, I just ordered the cases and let the first fall through to the second.





natural breast enlargement
psychic reading

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels

richious said...

Hi,

I have the same problems with the Exception. I think the biggest change from the printed book version is a very subtle one. I think i fixed it now.

First i thought there was some retain issue with the fetchedResultController or some missing break statement. But i figured out that in

controller:didChangeSection:
and
controller:didChangeObject:

the brackets in the if statements were set wrong, which would result in strange evaluation behaviour.
So you should check these lines (there are three of them) that the Negator (!) wraps all evaluations with brackets:

if (!((newSectionLocation == 0 && tableSectionCount == 1) && [self.tableView numberOfRowsInSection:0] == 0)) { ...
}


At least that fixed it for me :)

Cheers,
Richard