I need 1 sec delay in for loop but it is not working. I need to remove tableview cell with 1 sec delay with animation so it will remove one by one. Currently all rows are deleting at the same time.For loop is already in dispatch_after for 3 sec so over all it nested dispatch_after.Out side the for loop dispatch_after is working.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
for(int i=array_messages.count;i>0;i--)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[array_messages removeObjectAtIndex:0];
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.tableViewMessage deleteRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationTop];
});
}
});
Your for loop will iterate through the entire sequence almost instantly, which means your inner dispatch_after calls will all be set near the same time, and so will execute at around the same time, which is what you're seeing.
You would likely be better served in this case with an NSTimer. Something like this:
Create an NSTimer property to use:
#property (strong) NSTimer* deletionTimer = nil;
Add these methods to your class:
- (void)startDeletionTimer {
[self killDeletionTimer];
self.deletionTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(deletionTimerFired:) userInfo:nil repeats:YES];
}
- (void)killDeletionTimer {
[self.deletionTimer invalidate];
self.deletionTimer = nil;
}
- (void)deletionTimerFired:(NSTimer*)timer {
NSUInteger numberOfRecords = [array_messages count];
if (!numberOfRecords) {
// None left, we're done
[self killDeleteionTimer];
return;
}
[array_messages removeObjectAtIndex:0];
[self.tableViewMessage deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:0 inSection:indexPath.section]] withRowAnimation:UITableViewRowAnimationTop];
}
Initiate the timer with this:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self startDeletionTimer];
});
This has a few advantages over options using the inner dispatch_after with a delay. It will gracefully handle changes in the array_messages array since it's count is checked on each iteration, not assumed at the start. So for example, if you have 30 messages, your whole delete process will take 30 seconds. If a new message is added in that time period, or worse, a message is removed somehow, your app will crash when the last dispatch_after triggers, since the index and/or row won't exist. Similarly, if the user navigates away from the view, the tableView may be deallocated and you'll crash then.
Another advantage is if in those 30 seconds while it's slowly/painfully showing the records be deleted, the user wants to just move on, you can kill the timer and just delete all the rows at once.
You cannot put delay on 'for loop'. If you want to loop something with a delay use NSTimer.
Try this i am not practically tried so not sure
for(int i=array_messages.count;i>0;i--)
{
int delay = (array_messages.count - i) + 1;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[array_messages removeObjectAtIndex:0];
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.tableViewMessage deleteRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationTop];
});
}
Use the below code to delay the code execution
double delayInSeconds = 1.0;
dispatch_time_t disTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(disTime, dispatch_get_main_queue(), ^(void){
//place your code
});
Let me know if you have any issues.
# DJTiwari Try This it's work
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
dispatch_async(dispatch_get_main_queue(), ^(void){
for(int i=array_messages.count;i>0;i--)
{
[NSThread sleepForTimeInterval:1];
[array_messages removeObjectAtIndex:0];
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.tableViewMessage deleteRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationTop];
}
});
});
Related
I am trying to scroll to a particular UITableViewCell and it's works but sometimes if row is at the bottom of UITableView then it fails to scroll at that particular row. What I want to know is how I can identify which scroll position should be passed into the below method ?
- (void)scrollToObject:(AdvisoriesModel*)output {
NSInteger index = -1;
for (AdvisoriesModel* memberSites in self.AMAList) {
if ([memberSites.amaId isEqualToString:output.amaId]) {
index = [self.AMAList indexOfObject:memberSites];
break;
}
}
if (index != -1) {
#try {
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableAMA scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
[self.tableAMA selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Put your code here
[self.tableAMA deselectRowAtIndexPath:indexPath animated:YES];
});
} #catch (NSException *exception) {
NSLog(#"Exception occured while scrolling to Custom Fly Site %#",[exception description]);
}
}
}
I've noticed a few questions similar to this one on SO but they all seem to end in the person was calling
[self.tableView reloadData]
Outside of the main thread.
My problem is, I'm calling this on the main UI thread and I still don't get the new rows loaded until I like tap the screen or scroll a little bit. Here's what I'm doing: When I enter my ViewController the table displays fully and correctly. After pressing a button on the view I call my external db and load in some new rows to be added to the table. After adding these to my data source, I call reloadData and all of the new rows are blank. The couple rows that still fit on the screen that were already part of the table still show, but nothing new. When I touch the screen or scroll a little bit all of the new cells pop up.
Here's my code starting from when my call to the server finishes:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if(connection == self.searchConnection) {
..... some code ......
if(successful) {
// Adding new data to the datasource
[self.locations removeObjectAtIndex:[[NSNumber numberWithInt:self.locations.count - 1] integerValue]];
[self.cellTypeDictionary removeAllObjects];
for(int i = 0; i < [jsonLocations count]; ++i) {
NSDictionary *locationDictionary = jsonLocations[i];
BTRLocation *location = [BTRLocation parseLocationJSONObject:locationDictionary];
[self.locations addObject:location];
}
if(self.currentNextPageToken != nil && ![self.currentNextPageToken isEqual:[NSNull null]] && self.currentNextPageToken.length > 0) {
BTRLocation *fakeLocation = [[BTRLocation alloc] init];
[self.locations addObject:fakeLocation];
}
[self determineCellTypes];
if([self.locations count] < 1) {
self.emptyView.hidden = NO;
} else {
... some code .....
if(self.pendingSearchIsNextPageToken) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[[NSNumber numberWithInt:countBefore] integerValue]
inSection:[[NSNumber numberWithInt:0] integerValue]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
} else {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[[NSNumber numberWithInt:0] integerValue]
inSection:[[NSNumber numberWithInt:0] integerValue]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
}
}
} else {
if(invalidAccessToken) {
// TODO: invalidAccessToken need to log out
}
}
You can see I even added
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
Even though I know connectionDidFinishLoading is called on the main thread. But I figured I'd just try it anyway.
I can see no reason for why this isn't working.
the proper way is:
[self.tableView reloadData];
__weak MyViewController* weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong MyViewController *strongSelf = weakSelf;
[strongSelf.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
Sometimes I found that you add after the reloadData:
[self.tableView layoutIfNeeded];
Having a hard time figuring out how to add additional sections to my tableview when a user reaches the end of the currently loaded content. My table is setup in such a way that each piece of content is givien a section in the tableview and I have no clue how to go about adding say 50 new section to my table using a library like this :
https://github.com/samvermette/SVPullToRefresh
- (void)insertRowAtBottom {
__weak SVViewController *weakSelf = self;
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf.tableView beginUpdates];
[weakSelf.dataSource addObject:[weakSelf.dataSource.lastObject dateByAddingTimeInterval:-90]];
[weakSelf.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:weakSelf.dataSource.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
[weakSelf.tableView endUpdates];
[weakSelf.tableView.infiniteScrollingView stopAnimating];
});
}
First of I made my array Mutable so that I can just add objects to it. Then you also have to reload the tableview because you are changing the number of myArray.count I looked up what an NSIndexSet was and I made one for my array so I could set new sections for my tableview (add on the bottom or top) this is the full "block of code" that I used.
//Reload the tableview and update the array count
[self.tableView reloadData];
//Start the update
[weakSelf.tableView beginUpdates];
//Created the NSIndexSet to go into the next method
NSIndexSet *indexSet = [self.games indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop){
return [obj isKindOfClass:[NSString class]];
}];
//Inserting the new sections
[weakSelf.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationTop];
[weakSelf.tableView endUpdates];
//Stop the Animation
[weakSelf.tableView.pullToRefreshView stopAnimating];
If you do not reload the tableview your tableview WILL NOT update. It may even crash because there is some weird error that appears.
This sample app will crash after some time if you constantly rotate the device during the row animations. My real app crashes sometime even on the first rotate during the row animations.
How should I protect my app from crashing during rotation with simultanous row animations? Please don't suggest to forbid rotation until the animations are done. DataSource is dependent on network fetches which may take anything from 1 to 30 seconds depending on user network and user wants to rotate the device if he sees the app is better viewed in landscape for example right after launch.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[NSThread sleepForTimeInterval:0.2]; // imitates fetching and parsing
[self.array addObject:[NSString stringWithFormat:#"cell number %d", i]];
dispatch_async(dispatch_get_main_queue(), ^{
// perform on main
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
});
}
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.array.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = self.array[indexPath.row];
return cell;
}
- (NSMutableArray *)array
{
if (!_array) {
_array = [[NSMutableArray alloc] init];
}
return _array;
}
Crash report
2014-02-21 12:47:24.667 RowsAnimationRotate[2062:60b] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2903.23/UITableView.m:1330
2014-02-21 12:47:24.673 RowsAnimationRotate[2062:60b] *** 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 (8) must be equal to the number of rows contained in that section before the update (8), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
You have essentially created a race condition.
The problem is you are manipulating self.array in a background thread while self.tableView insertRowsAtIndexPaths is running on the main thread and will be accessing self.array.
So at some pointself.tableView insertRowsAtIndexPaths (or other tableView methods called as a result of this) is running on the main thread, expecting a certain number of objects in self.array, but the background thread gets in there and adds another one...
To fix your simulation:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[NSThread sleepForTimeInterval:0.2]; // imitates fetching and parsing
NSString *myNewObject = [NSString stringWithFormat:#"cell number %d", i]];
dispatch_async(dispatch_get_main_queue(), ^{
// perform on main
[self.array addObject: myNewObject];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
});
}
});
can you change
dispatch_async(dispatch_get_main_queue(), ^{
// perform on main
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
});
with:
dispatch_async(dispatch_get_main_queue(), ^{
// perform on main
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
Can you please try your simulation with synchronized:-
It declares a critical section around the code block. In multithreaded code, #synchronized guarantees that only one thread can be executing that code in the block at any given time.
#synchronized(self.tableView) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[NSThread sleepForTimeInterval:0.2]; // imitates fetching and parsing
[self.array addObject:[NSString stringWithFormat:#"cell number %d", i]];
dispatch_async(dispatch_get_main_queue(), ^{
// perform on main
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
});
}
});
}
Hope that helps. Please let me know if we have to go with another solution.
I have already 10 rows in TableView What I am trying to do is adding another 10 rows for that I am using insertRowsAtIndexPaths but I am getting errors.
Following is the code I am using
-(void)insertDownloadedActions:(NSMutableArray *)dataToAdd
{
__weak CurrentViewController *weakSelf = self;
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf.tableView beginUpdates];
[weakSelf.dataSource addObjects:dataToAdd];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[weakSelf.dataSource count]-dataToAdd.count-1 inSection:0];
[weakSelf.tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationTop];
[weakSelf.tableView endUpdates];
});
}
But I am getting following error for that
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 (20) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
The code is close, but the table view needs to be updated with index paths in exact correspondence with what's added to the datasource.
-(void)insertDownloadedActions:(NSMutableArray *)dataToAdd
{
// don't need this
//__weak CurrentViewController *weakSelf = self;
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
// build the index paths for insertion
// since you're adding to the end of datasource, the new rows will start at count
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger currentCount = self.datasource.count;
for (int i = 0; i < dataToAdd.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:currentCount+i inSection:0]];
}
// do the insertion
[self.dataSource addObjects:dataToAdd];
// tell the table view to update (at all of the inserted index paths)
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
});
}
You want a weakSelf to avoid cycle where the block owner retains the block and the block (by using the block owner "self") retains the owner. There's no need for the weakSelf pattern here since the view controller is not retaining a copy of the dispatched block.
In swift, to add multiple rows, We can do
let indexPaths = (0 ..< messageList.count).map { IndexPath(row: $0, section: 0) }
self.chatTableView.beginUpdates()
self.chatTableView.insertRows(at: indexPaths, with: .bottom)
self.chatTableView.endUpdates()
Here I'm inserting to indexPath:0 as I want to append the list on scrolling up. (Reverse pagination)
The insertRowsAtIndexPaths:withRowAnimation: AND the changes to your data model both need to occur in-between beginUpdates and endUpates
I've created a simple example that should work on its own. I spent a week fiddling around trying to figure this out since I couldn't find any simple examples, so I hope this saves someone time and headache!
#interface MyTableViewController ()
#property (nonatomic, strong) NSMutableArray *expandableArray;
#property (nonatomic, strong) NSMutableArray *indexPaths;
#property (nonatomic, strong) UITableView *myTableView;
#end
#implementation MyTableViewController
- (void)viewDidLoad
{
[self setupArray];
}
- (void)setupArray
{
self.expandableArray = #[#"One", #"Two", #"Three", #"Four", #"Five"].mutableCopy;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.expandableArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//here you should create a cell that displays information from self.expandableArray, and return it
}
//call this method if your button/cell/whatever is tapped
- (void)didTapTriggerToChangeTableView
{
if (/*some condition occurs that makes you want to expand the tableView*/) {
[self expandArray]
}else if (/*some other condition occurs that makes you want to retract the tableView*/){
[self retractArray]
}
}
//this example adds 1 item
- (void)expandArray
{
//create an array of indexPaths
self.indexPaths = [[NSMutableArray alloc] init];
for (int i = theFirstIndexWhereYouWantToInsertYourAdditionalCells; i < theTotalNumberOfAdditionalCellsToInsert + theFirstIndexWhereYouWantToInsertYourAdditionalCells; i++) {
[self.indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
//HERE IS WHERE YOU NEED TO ALTER self.expandableArray to have the additional/new data values, eg:
[self.expandableArray addObject:#"Six"];
[self.myTableView insertRowsAtIndexPaths:self.indexPaths withRowAnimation:(UITableViewRowAnimationFade)]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
//this example removes all but the first 3 items
- (void)retractArray
{
NSRange range;
range.location = 3;
range.length = self.expandableArray.count - 3;
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
[self.expandableArray removeObjectsInRange:range];
[self.myTableView deleteRowsAtIndexPaths:self.indexPaths withRowAnimation:UITableViewRowAnimationFade]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
#end
Free code, don't knock it.
I am not sure of this, but just try this may be it will solve your problem, this may be the problem that you are adding data after calling begin updates. So just update the data source before begin updates.