UISegmentedControl Changed Event misfires when adding a method inside - iphone

Here is my code for the UIControlEventValueChanged event for a UISegmentedControl:
- (void)segmentAction:(id)sender{
if([sender selectedSegmentIndex] == 0){
pendingIsShowing = YES;
[freeCutsTable reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
}else if([sender selectedSegmentIndex] == 1){
pendingIsShowing = NO;
[self showAvailableCuts:sender];
[freeCutsTable reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
}
}
My problem is when the value is changed, the [self showAvailableCuts:sender] is called, but the segment control no longer changes its index. If I comment out the method call, it works fine...What I tried to do was pass in the segmented control into [self showAvailableCuts:sender] and change it that way, but to no avail...
- (void)showAvailableCuts:(id)sender{
if(!pendingIsShowing){
NSString *path =#"https://WebserviceURL";
NSString* escapedUrl = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
//[sender setSelectedIndex:1];
NSLog(#"%#", escapedUrl);
[self parseXMLFileAtURL:escapedUrl];
}
}
I'm not sure why this is occurring...

Without looking at the code for [self showAvailableCuts:sender]; all I can think of is - why do you need to pass the 'sender' object itself? Just modify the showAvailableCuts method so that you need to pass only the required value(s) like [sender titleForSegmentAtIndex:] , [sender selectedSegmentIndex] etc.
ok, firstly the setter should be [sender setSelectedSegmentIndex:index] not what you have commented out. I have myself successfully used this -
sender.selectedSegmentIndex = index; //in your case index is 1

Related

Cannot disable multi touch on Uitable View (ios7)

I have an problem that i use custom cell for UITableView, when I tap more than one finger (2 fingers or more) on my tableview it had many problems some of my labels on each cells (to display information) lost texts (it's empty). So that I try to disable multi touch on my table, but it's not affect. I try to add tableView.allowsMultipleSelection = NO; or tableView.multipleTouchEnabled = NO; into cellForRowAtIndexPath or didSelectRowAtIndexPath. But nothing work. Please help me to find out solution.
Thank you!
Here is my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = indexPath.row;
#synchronized (self) {
if (row == [voicemailItems count]) {
// User selected the blank rows
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Blank out the play button on previous selected row
[self deselect];
return;
}
}
if (selectedRowIndexPath != nil) {
if (row == selectedRowIndexPath.row) {
// Selecting the same row twice will play the voicemail
if (streaming == NO) {
if (calling == NO) {
// Play the voicemail
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(playVoicemailAction:) userInfo:indexPath repeats:NO];
}
return;
}
else {
// Streaming VM
if ([self isCallInProgress] == YES) {
[ScreenUtils errorAllert:#"Cannot play voicemail while call is in progress." type:kUINotice delegate:self];
}
else {
if (![self isVoicemailNotification:selectedRowIndexPath.row]) {
// Stream the voicemail
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(playVoicemailAction:) userInfo:indexPath repeats:NO];
}
}
}
}
else {
// Selecting a different row
[self shutdownPlayer];
[self cancel];
// Blank out the play button on previous selected row
[self deselect];
}
}
selectedRowIndexPath = indexPath;
// Enable Call Back button
// Don't enable if private, etc.
btnCallBack.enabled = ([self canCallBack:row] &&
!calling &&
([self isCallInProgress] == NO) &&
![self isVoicemailNotification:selectedRowIndexPath.row]);
// Enable and Delete button
btnDelete.enabled = YES;
// Select the cell
VoicemailCell * cell = (VoicemailCell*)[tblView cellForRowAtIndexPath:indexPath];
[cell select:YES playing:[self isPlaying] stream:streaming];
[tblView setNeedsDisplay];
//[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Try this, it helps me!
cell.contentView.exclusiveTouch = YES;
cell.exclusiveTouch = YES;
#try this
[cell setExclusiveTouch:YES]
after many tries, I find out that I need to add the follow code at the end of didSelectRowAtIndexPath:
[tableView deselectRowAtIndexPath:indexPath animated:YES];

Weird glitch in UITableView section header when reloading section above

Have a look at this video.
I get this glitch when running this code:
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"following == YES"];
[self.fetchedResultsController.fetchRequ­est setPredicate:predicate];
[self.fetchedResultsController performFetch:nil];
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:THE_SECTION_WITH_THE_B­LOGS] withRowAnimation:UITableViewRowAnimation­Automatic];
[self.tableView endUpdates];
Why does the section header below the section I reload change it frame like this? Any solutions?
EDIT:
This is my viewForHeaderInSection:
(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (tableView != self.tableView)
return nil;
BLMenuTableSectionHeaderView *view = nil;
if (section != BLMenuSectionUser) {
view = [[BLMenuTableSectionHeaderView alloc] initWithFrame:CGRectZero];
if (section == BLMenuSectionFollowing){
if ([self blogCount] > 0){
[view setTitle:NSLocalizedString(#"Following", nil)];
[view addSubview:self.editButton];
}else{
self.tableView.editing = NO;
}
}
if (section == BLMenuSectionPosts){
[view setTitle:NSLocalizedString(#"Browse", nil)];
}
else if (section == BLMenuSectionSettings)
[view setTitle:NSLocalizedString(#"Settings", nil)];
}
return view;
}
Afaik, you shouldn't use -beginUpdates and -endUpdates in conjuction with a reload, but rather when inserting or deleting.
Not sure if this is causing your problems, but it might be worth checking out.
Is it the same regardless of animation type?
From Apple's docs:
Call this method if you want subsequent insertions, deletion, and
selection operations (for example, cellForRowAtIndexPath: and
indexPathsForVisibleRows) to be animated simultaneously. This group of
methods must conclude with an invocation of endUpdates. These method
pairs can be nested. If you do not make the insertion, deletion, and
selection calls inside this block, table attributes such as row count
might become invalid. You should not call reloadData within the group;
if you call this method within the group, you will need to perform any
animations yourself.

UITableView adding rows with animation one at a time with notable delay

I am trying to add 10 rows to a tableView but instead of reloadData i m using insertRowsAtIndesPaths in a for loop, but instead of adding one row at a time, it adds all 10 at the end. Heres my code...
if([[[[[notification userInfo] objectForKey:#"cityevent"]objectForKey:#"events"]objectForKey:#"event"] isKindOfClass:[NSArray class]])
{
[self.featuredEventTableView beginUpdates];
for(NSDictionary *tempDict in [[[[notification userInfo] objectForKey:#"cityevent"]objectForKey:#"events"]objectForKey:#"event"])
{
ESEvent *event=[[ESEvent alloc]init];
event.name=[tempDict objectForKey:#"name"];
if([[tempDict objectForKey:#"image"]isKindOfClass:[NSNull class]] || [tempDict objectForKey:#"image"] ==nil)
event.image=[UIImage imageNamed:#"category_default.png"];
else
{
event.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[tempDict objectForKey:#"image"]]]];
}
[events addObject:event];
NSIndexPath *ip=[NSIndexPath indexPathForRow:([events count]-1) inSection:0];
NSLog(#"row: %i section: %i",ip.row,ip.section);
[self.featuredEventTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationRight];
[self.featuredEventTableView endUpdates];
}
}
Any suggestions?
Move your start updates call to just before the insertRowsAtIndexPaths: call. See if that helps. I'm surprised this works at all because you call end update multiple times but start only once.
Actually this will probably still be so fast that you don't even notice. Look into NSTimer or GCD's dispatch_after to stagger the updates.
The for loop doesn't matter in this case - the OS accumulates all animation actions and commits them together. To do this right, you'll have to use animation stop selector and call an external method to add the next cell until all cells have been added.
why you are not using reloadData ... I guess this will help you in what you want to display on your UI. If you use for loop and add one data into your actual array and at end if you call reload, it will give you that effect.
If you want to continue with your existing code than try adding [NSThread sleepForTimeInterval:0.5]; after i guess [self.featuredEventTableView endUpdates];. I m not on my mac so can't test it. This will pause the current thread for some time, so it will give that animation effect.
EDIT Try code below
if([[[[[notification userInfo] objectForKey:#"cityevent"]objectForKey:#"events"]objectForKey:#"event"] isKindOfClass:[NSArray class]])
{
for(NSDictionary *tempDict in [[[[notification userInfo] objectForKey:#"cityevent"]objectForKey:#"events"]objectForKey:#"event"])
{
ESEvent *event=[[ESEvent alloc]init];
event.name=[tempDict objectForKey:#"name"];
if([[tempDict objectForKey:#"image"]isKindOfClass:[NSNull class]] || [tempDict objectForKey:#"image"] ==nil)
event.image=[UIImage imageNamed:#"category_default.png"];
else
{
event.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[tempDict objectForKey:#"image"]]]];
}
[events addObject:event];
NSIndexPath *ip=[NSIndexPath indexPathForRow:([events count]-1) inSection:0];
NSLog(#"row: %i section: %i",ip.row,ip.section);
// If this don't work then add thread sleep code at here.
[self.featuredEventTableView beginUpdates];
[self.featuredEventTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationRight];
[self.featuredEventTableView endUpdates];
}
}
None of the answers worked. I used NSTimer as follows...
tableTimer=[NSTimer scheduledTimerWithTimeInterval:0.4f target:self selector:#selector(performTableUpdates:) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:i],#"count", nil] repeats:YES];
and
-(void)performTableUpdates:(NSTimer*)timer
{
NSLog(#"%i",i);
NSIndexPath *ip=[NSIndexPath indexPathForRow:i inSection:0];
[newArray addObject:[events objectAtIndex:i]];
[self.featuredEventTableView beginUpdates];
[self.featuredEventTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationLeft];
[self.featuredEventTableView endUpdates];
if(i<[events count])
{
if([events count]-i==1)
{
[tableTimer invalidate];
i++;
}
else
i++;
}
}

Pass a string from DidSelectRowAtIndexPath to a button-click action

I have this code
NSString *localStringValue;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
localStringValue = [m_textfield cellForRowAtIndexPath:indexPath].textLabel.text;
localStringValue = [m_textfield cellForRowAtIndexPath:indexPath].detailTextLabel.text;
NSArray* toReload = [NSArray arrayWithObjects: indexPath, self.selectedIndexPath, nil];
self.selectedIndexPath = indexPath;
if ([[tableView cellForRowAtIndexPath:indexPath] accessoryType] == UITableViewCellAccessoryCheckmark){
UploadView *uploadview = (UploadView *)self.view;
if (uploadview != nil)
{
[m_owner uploadString:localStringValue];
//[self dismissModalViewControllerAnimated:YES];
}
[[m_textfield cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryNone];
}
else {
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
}
}
in this code i am syncing localStringValue to google-doc when i tap the cell if the check mark is there.localStringValue contains the values in the tableview cell.Every thing works fine at this point.But my need is i want to pass this value to a button click,that means if the user select multiple row i want all the values in the localStringValue and pass this through this code
- (IBAction)doUpload:(id)sender
{
UploadView *uploadview = (UploadView *)self.view;
if (uploadview != nil)
{
[m_owner uploadString:#""];
//[self dismissModalViewControllerAnimated:YES];
}
}
i want to pass localstringvalue in [m_owner uploadString:localstringvalue];
How to do this?
thanks in advance.
you can create a NSMutableArray as class variable and you can add your strings to that on didSelectRowAtIndexPath. Later on on button click you can process the NSMutableArray to fetch strings one by one and sending them to google-doc...etc.
You need to update ur
- (void)doUpload:(NSString*)stringValue
{
UploadView *uploadview = (UploadView *)self.view;
if (uploadview != nil)
{
[m_owner uploadString:localstringvalue];
//[self dismissModalViewControllerAnimated:YES];
}
}
And one more thing
localStringValue = [m_textfield cellForRowAtIndexPath:indexPath].textLabel.text // reassigning the string again so this line does not make any sense.

Rearranging UITableView with Core Data [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to implement re-ordering of CoreData records?
I'm trying to find a code sample that shows how to handle moving/rearranging cells in a tableView when the cell uses a fetchedResultsController (i.e. in conjunction with Core Data). I'm getting the moveRowAtIndexPath: call to my data source, but I can't find the right combination of black magic to get the table/data to recognize the change properly.
For example, when I move row 0 to row 2 and then let go, it "looks" correct. Then I click "Done". The row (1) that had slid up to fill row 0 still has it's editing mode appearance (minus and move icons), while the other rows below slide back to normal appearance. If I then scroll down, as row 2 (originally 0, remember?) nears the top, it completely disappears.
WTF. Do I need to somehow invalidate the fetchedResultsController? Whenever I set it to nil, I get crashes. Should I release it instead? Am I in the weeds?
Here's what I've currently got in there...
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
/*
Update the links data in response to the move.
Update the display order indexes within the range of the move.
*/
if (fromIndexPath.section == toIndexPath.section) {
NSInteger start = fromIndexPath.row;
NSInteger end = toIndexPath.row;
NSInteger i = 0;
if (toIndexPath.row < start)
start = toIndexPath.row;
if (fromIndexPath.row > end)
end = fromIndexPath.row;
for (i = start; i <= end; i++) {
NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section];
LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath];
//[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]];
link.order = [NSNumber numberWithInteger:i];
[managedObjectContext refreshObject:link mergeChanges:YES];
//[managedObjectContext insertObject:link];
}
}
// Save the context.
NSError *error;
if (![context save:&error]) {
// Handle the error...
}
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
if (self.theTableView != nil)
[self.theTableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
if (self.theTableView != nil) {
[self.theTableView endUpdates];
}
}
Usually when you see artifacts like that what is going on is the UI has animated to a new position and told you about it, then the updates you have done to your model don't correctly reflect the state which results in glitches the next time the view has to refer to the model for an update.
I think you don't exactly understand what you are supposed to do in the method. It is called because the UI has changed and it needs to let the model to change accordingly. The code below presumes the results are already in the new order and you just need to reset the order field for some reason:
for (i = start; i <= end; i++) {
NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section];
LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath];
//[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]];
link.order = [NSNumber numberWithInteger:i];
[managedObjectContext refreshObject:link mergeChanges:YES];
//[managedObjectContext insertObject:link];
}
The catch is that you are not actually changing the order in the underlying model. Those indexPaths are from UITableViewController, it is telling you that the user dragged between those to spots and you need to update the underlying data according. But the fetchedResultsController is always in sort order, so until you have changed those properties nothing has moved.
The thing is, they have not been moved, you are being called to tell you that you need to move them around (by adjusting the sortable property). You really need to something more like:
NSNumber *targetOrder = [fetchedResultsController objectAtIndexPath:toIndexPath];
LinkObj *link = [fetchedResultsController objectAtIndexPath:FromPath];
link.order = targetOrder;
Which will cause the objects to reorder, then go through and clean up any of the order numbers of other objects that should have shifted up, being aware the indexes may have moved.
Here's what's officially working now, with deletes, moves, and inserts. I "validate" the order any time there's an edit action affecting the order.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section != kHeaderSection) {
if (editingStyle == UITableViewCellEditingStyleDelete) {
#try {
LinkObj * link = [self.fetchedResultsController objectAtIndexPath:indexPath];
debug_NSLog(#"Deleting at indexPath %#", [indexPath description]);
//debug_NSLog(#"Deleting object %#", [link description]);
if ([self numberOfBodyLinks] > 1)
[self.managedObjectContext deleteObject:link];
}
#catch (NSException * e) {
debug_NSLog(#"Failure in commitEditingStyle, name=%# reason=%#", e.name, e.reason);
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// we need this for when they click the "+" icon; just select the row
[theTableView.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
}
}
}
- (BOOL)validateLinkOrders {
NSUInteger index = 0;
#try {
NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects];
if (fetchedObjects == nil)
return NO;
LinkObj * link = nil;
for (link in fetchedObjects) {
if (link.section.intValue == kBodySection) {
if (link.order.intValue != index) {
debug_NSLog(#"Info: Order out of sync, order=%# expected=%d", link.order, index);
link.order = [NSNumber numberWithInt:index];
}
index++;
}
}
}
#catch (NSException * e) {
debug_NSLog(#"Failure in validateLinkOrders, name=%# reason=%#", e.name, e.reason);
}
return (index > 0 ? YES : NO);
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects];
if (fetchedObjects == nil)
return;
NSUInteger fromRow = fromIndexPath.row + NUM_HEADER_SECTION_ROWS;
NSUInteger toRow = toIndexPath.row + NUM_HEADER_SECTION_ROWS;
NSInteger start = fromRow;
NSInteger end = toRow;
NSInteger i = 0;
LinkObj *link = nil;
if (toRow < start)
start = toRow;
if (fromRow > end)
end = fromRow;
#try {
for (i = start; i <= end; i++) {
link = [fetchedObjects objectAtIndex:i]; //
//debug_NSLog(#"Before: %#", link);
if (i == fromRow) // it's our initial cell, just set it to our final destination
link.order = [NSNumber numberWithInt:(toRow-NUM_HEADER_SECTION_ROWS)];
else if (fromRow < toRow)
link.order = [NSNumber numberWithInt:(i-1-NUM_HEADER_SECTION_ROWS)]; // it moved forward, shift back
else // if (fromIndexPath.row > toIndexPath.row)
link.order = [NSNumber numberWithInt:(i+1-NUM_HEADER_SECTION_ROWS)]; // it moved backward, shift forward
//debug_NSLog(#"After: %#", link);
}
}
#catch (NSException * e) {
debug_NSLog(#"Failure in moveRowAtIndexPath, name=%# reason=%#", e.name, e.reason);
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
#try {
switch (type) {
case NSFetchedResultsChangeInsert:
[theTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self validateLinkOrders];
break;
case NSFetchedResultsChangeUpdate:
break;
case NSFetchedResultsChangeMove:
self.moving = YES;
[self validateLinkOrders];
break;
case NSFetchedResultsChangeDelete:
[theTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self validateLinkOrders];
break;
default:
break;
}
}
#catch (NSException * e) {
debug_NSLog(#"Failure in didChangeObject, name=%# reason=%#", e.name, e.reason);
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.theTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.theTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
#try {
if (self.theTableView != nil) {
//[self.theTableView endUpdates];
if (self.moving) {
self.moving = NO;
[self.theTableView reloadData];
//[self performSelector:#selector(reloadData) withObject:nil afterDelay:0.02];
}
[self performSelector:#selector(save) withObject:nil afterDelay:0.02];
}
}
#catch (NSException * e) {
debug_NSLog(#"Failure in controllerDidChangeContent, name=%# reason=%#", e.name, e.reason);
}
}
The best answer is actually in Clint Harris's comment on the question:
http://www.cimgf.com/2010/06/05/re-ordering-nsfetchedresultscontroller
To quickly summarise, the essential part is to have a displayOrder property on the objects you are trying to rearrange with the sort description for the fetched results controller ordering on that field. The code for moveRowAtIndexPath:toIndexPath: then looks like this:
- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toIndexPath:(NSIndexPath *)destinationIndexPath;
{
NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy];
// Grab the item we're moving.
NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath];
// Remove the object we're moving from the array.
[things removeObject:thing];
// Now re-insert it at the destination.
[things insertObject:thing atIndex:[destinationIndexPath row]];
// All of the objects are now in their correct order. Update each
// object's displayOrder field by iterating through the array.
int i = 0;
for (NSManagedObject *mo in things)
{
[mo setValue:[NSNumber numberWithInt:i++] forKey:#"displayOrder"];
}
[things release], things = nil;
[managedObjectContext save:nil];
}
The Apple documentation also contains important hints:
https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html
This is also mentioned in How to implement re-ordering of CoreData records?
To quote the Apple documentation:
User-Driven Updates
In general, NSFetchedResultsController is designed to respond to
changes at the model layer. If you allow a user to reorder table rows,
then your implementation of the delegate methods must take this into
account.
Typically, if you allow the user to reorder table rows, your model
object has an attribute that specifies its index. When the user moves
a row, you update this attribute accordingly. This, however, has the
side effect of causing the controller to notice the change, and so
inform its delegate of the update (using
controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:).
If you simply use the implementation of this method shown in “Typical
Use,” then the delegate attempts to update the table view. The table
view, however, is already in the appropriate state because of the
user’s action.
In general, therefore, if you support user-driven updates, you should
set a flag if a move is initiated by the user. In the implementation
of your delegate methods, if the flag is set, you bypass main method
implementations; for example:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
if (!changeIsUserDriven) {
UITableView *tableView = self.tableView;
// Implementation continues...
When you move a row in the table view, you actually move a block of other rows (consisting of at least one row) into the other direction at the same time. The trick is to only update the displayOrder property of this block and of the moved item.
First, ensure that the displayOrder property of all rows is set according to the tables current display order. We don't have to save the context here, we will save it later when the actual move operation finished:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[_tableView setEditing:editing animated:animated];
if(editing) {
NSInteger rowsInSection = [self tableView:_tableView numberOfRowsInSection:0];
// Update the position of all items
for (NSInteger i=0; i<rowsInSection; i++) {
NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath];
NSNumber *newPosition = [NSNumber numberWithInteger:i];
if (![curObj.displayOrder isEqualToNumber:newPosition]) {
curObj.displayOrder = newPosition;
}
}
}
}
Then the only thing you have to do is to update the position of the moved item and of all items between fromIndexPath and toIndexPath:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
NSInteger moveDirection = 1;
NSIndexPath *lowerIndexPath = toIndexPath;
NSIndexPath *higherIndexPath = fromIndexPath;
if (fromIndexPath.row < toIndexPath.row) {
// Move items one position upwards
moveDirection = -1;
lowerIndexPath = fromIndexPath;
higherIndexPath = toIndexPath;
}
// Move all items between fromIndexPath and toIndexPath upwards or downwards by one position
for (NSInteger i=lowerIndexPath.row; i<=higherIndexPath.row; i++) {
NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:fromIndexPath.section];
SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath];
NSNumber *newPosition = [NSNumber numberWithInteger:i+moveDirection];
curObj.displayOrder = newPosition;
}
SomeManagedObject *movedObj = [_fetchedResultsController objectAtIndexPath:fromIndexPath];
movedObj.displayOrder = [NSNumber numberWithInteger:toIndexPath.row];
NSError *error;
if (![_fetchedResultsController.managedObjectContext save:&error]) {
NSLog(#"Could not save context: %#", error);
}
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
[self.pairs exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
[self performSelector:#selector(reloadData) withObject:nil afterDelay:0.02];
}
- (void)reloadData{
[table reloadData];
}
The table can not reload while it is moving, reload after a delay and you will be fine.
Sorry Greg, I am sure I am doing something wrong, but you answer doesn't work for me.
Although all my objects validate properly, when I exit edit mode, one of the rows freezes (the editing controls don't disappear) and the cells don't respond properly afterwards.
Maybe my problem is that I don't know how to use the moving property that you set (self.moving = YES). Could you please clarify this? Thank you very much.
Jorge
This is very difficult way to implement such functionality.
Much easier and elegant way can be found here:
UITableView Core Data reordering
[<> isEditing] can be used to determine whether editing of the table is enabled or not. Rather than delaying it as suggested by using the following statement
[table performSelector:#selector(reloadData) withObject:nil afterDelay:0.02];