Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically changing data source causing deleteRowsAtIndexPaths:indexes to crash

Tearing my hair out trying to get this to work. I want to perform [self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];,

More detailed code of how I delete:

int index =  (int)[self.messages indexOfObject:self.messageToDelete];


[self.messages removeObject:self.messageToDelete];

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
NSArray *indexes = [[NSArray alloc] initWithObjects:indexPath, nil];

[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationLeft];

This works fine however if I get a push notification (i.e a new message received) whilst deleting the app will crash and display an error like:

Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-3347.44/UITableView.m:1327 2015-07-04 19:12:48.623 myapp[319:24083] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 1 from section 0 which only contains 1 rows before the update'

I suspect this is because my data source is changing, the size of the array that

 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

references while deleting won't be consistent because it was incremented by one when the push notification triggered a refresh. Is there any way I can work around this? Am I correct that deleteRowsAtIndexPaths uses the numberOfRowsInSection method?

like image 718
Kex Avatar asked Jun 30 '15 17:06

Kex


People also ask

How to do dynamic change of data source on connection string?

Parameterize your connection string with query parameter then you can achieve dynamic change the data source on power bi service side. But this seems not suitable for your scenario, it look more complex than change on single data source.

Is it possible to dynamically change a data source at runtime?

Unfortunately, it isn't really possible to carry out what you want to achieve. I think if PowerApps provided the ability for users to dynamically change a data source at runtime, it would be a great addition to the product.

Can PowerApps dynamically change a data source at runtime?

I think if PowerApps provided the ability for users to dynamically change a data source at runtime, it would be a great addition to the product. The workarounds that people use generally use involve adding all possible data sources to an app and providing some dropdown to load the data source into a collection.

Is there a way to decouple data paths in Power BI report?

But as you know, Power BI will complain that the file paths of the data sources do not exist. Is there a way to decouple the data paths in the report, so it points to the just the subfolder and won't require a full path including the C:\User portion of it? Solved! Go to Solution. 12-27-2018 12:36 AM Yes, your requirement can be achieved.


2 Answers

So, in order to solve your problem you need to ensure that your data source will not change while some table view animations are in place. I would propose to do the following.

First, create two arrays: messagesToDelete and messagesToInsert. These will hold information about which messages you want to delete/insert.

Second, add a Boolean property updatingTable to your table view data source.

Third, add the following functions:

-(void)updateTableIfPossible {
    if (!updatingTable) {
        updatingTable = [self updateTableViewWithNewUpdates];
    }
}

-(BOOL)updateTableViewWithNewUpdates {
     if ((messagesToDelete.count == 0)&&(messagesToInsert.count==0)) {
         return false;
     }
     NSMutableArray *indexPathsForMessagesThatNeedDelete = [[NSMutableArray alloc] init];
     NSMutableArray *indexPathsForMessagesThatNeedInsert = [[NSMutableArray alloc] init];
     // for deletion you need to use original messages to ensure
     // that you get correct index paths if there are multiple rows to delete
     NSMutableArray *oldMessages = [self.messages copy];
     for (id message in messagesToDelete) {
         int index =  (int)[self.oldMessages indexOfObject:message];
         [self.messages removeObject:message];
         NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
         [indexPathsForMessagesThatNeedDelete addObject:indexPath];
     }
     for (id message in messagesToInsert) {
         [self.messages insertObject:message atIndex:0];
         NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
         [indexPathsForMessagesThatNeedInsert addObject:indexPath];
     }
     [messagesToDelete removeAllObjects];
     [messagesToInsert removeAllObjects];

     // at this point your messages array contains
     // all messages which should be displayed at CURRENT time

     // now do the following

     [CATransaction begin];

     [CATransaction setCompletionBlock:^{
         updatingTable = NO;
         [self updateTableIfPossible];
     }];

    [tableView beginUpdates];

    [tableView deleteRowsAtIndexPaths:indexPathsForMessagesThatNeedDelete withRowAnimation:UITableViewRowAnimationLeft];
    [tableView insertRowsAtIndexPaths:indexPathsForMessagesThatNeedInsert withRowAnimation:UITableViewRowAnimationLeft];

    [tableView endUpdates];

    [CATransaction commit];
    return true;
}

Lastly, you need to have the following code in all functions which want to add/delete rows.

To add message

[self.messagesToInsert addObject:message];
[self updateTableIfPossible];

To delete message

[self.messagesToDelete addObject:message];
[self updateTableIfPossible];

What this code does is ensures stability of your data source. Whenever there is a change you add the messages that need to be inserted/deleted into arrays (messagesToDelete and messagesToDelete). You then call a function updateTableIfPossible which will update the table view's data source (and will animate the change) provided that there is no current animation in progress. If there is an animation in progress it will do nothing at this stage.

However, because we have added a completion

[CATransaction setCompletionBlock:^{
     updatingTable = NO;
     [self updateTableIfPossible];
}];

at the end of the animations our data source will check if there are any new changes that need to be applied to the table view and, if so, it will update the animation.

This is a much safer way of updating your data source. Please let me know if it works for you.

like image 177
Andriy Gordiychuk Avatar answered Oct 14 '22 07:10

Andriy Gordiychuk


Delete a Row

int index =  (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];

[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];

Delete a Section

Note : if your TableView have multiple section than you have to delete whole section when section contain only one row instead of deleting row

int index =  (int)[self.messages indexOfObject:self.messageToDelete];
[self.messages removeObject:self.messageToDelete];

NSIndexPath *indexPath = [NSIndexSet indexSetWithIndex:0]; 

[tableView beginUpdates];
[tableView deleteSections:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
like image 36
Kirit Vaghela Avatar answered Oct 14 '22 05:10

Kirit Vaghela