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.
Related
I have a chat application, in which i want to increase the index path by one when new chat data come. But it is not happening through my code I am sharing code with screen shot please help.
NSUInteger messageCount = [self numberOfMessages];
if (self.conversation && messageCount > 0) {
NSLog(#"%ld",(long)indexPathValue.row);
NSIndexPath* ip = [NSIndexPath indexPathForRow:indexPathValue.row + 1 inSection:0];
NSLog(#"%ld",(long)ip.row);
[layerChatTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
Below code allows you to go last indexpath with scroll position of bottom.
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:messageCoutnt-1 inSection:0] atScrollPosition:UITableViewScrollPositionButtom animated:NO];
Try and let me know
I am assuming that you have handled the datasource updating part and that you are updating the table view on main thread.
You can try to add new row at bottom like this:
NSInteger section = 0;
NSInteger newCount = [self tableView:self.tableView numberOfRowsInSection:section];
NSMutableArray *paths = #[].mutableCopy;
[paths addObject:[NSIndexPath indexPathForRow:newCount inSection:section]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
You can also add more than 1 row with some modifications.
Hope this helps.
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];
}
});
});
I am removing tableview rows using [tableView beginUpdates] and [tableView endUpdates]
The mechanism works just fine.
My only problem is that visually when the cell is disappearing from the view, it is sliding on top of the cell above it, instead of behind it.
How can I make the cell go under the cell above it, instead of visually above it?
Here is my code:
-(void)reloadTableRowsWithAnimation {
NSMutableArray<NSIndexPath *> *selectedIndexPaths = [[NSMutableArray<NSIndexPath *> alloc]init];
[self.appsDataSource.selectedRows enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
[selectedIndexPaths addObject:[NSIndexPath indexPathForRow:idx inSection:0]];
}];
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:selectedIndexPaths
withRowAnimation: UITableViewRowAnimationTop];
[self reloadAppsBeforeReloadTableView];
[self.tableView endUpdates];
}
I want to add rows to a tableview with begin/endUpdates to prevent the jump of the tableview when i do reloadData
this is my code
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
[self.messagesTableView beginUpdates];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
tableViewOffset.y += heightForNewRows;
[self.messagesTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
And sometimes (not everytime) I get this error
invalid number of rows in section 0.
The number of rows contained in an existing section after
the update (2) must be equal to the number of rows contained in that section
before the update (2), plus or minus the number of rows inserted or deleted
from that section (2 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
How do i prevent this error ?
if you dont want animation then use directly
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[self.messagesTableView reloadData];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
If you want to quickly jump to a selected indexPath (with or without animation), you can use this function:
//My function to populate
//tableView is synthesized
-(void)setNewMessage:(NSString*)message{
// ...
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
// if your selectedIndexPath==nil, the table scroll stay in the same position
[self reloadData:selectedIndexPath animated:NO];
}
//My reload data wrapper
-(void)reloadData:(NSIndexPath *)selectedIndexPath animated:(BOOL)animated{
[tableView reloadData];
// atScrollPosition can receive different parameters (eg:UITableViewScrollPositionMiddle)
[tableView scrollToRowAtIndexPath:selectedIndexPath
atScrollPosition:UITableViewScrollPositionTop
animated:animated];
}
Once I needed that same flexibility in a tableView and this function has met my needs. You don`t need the "setAnimationsEnabled", just use:
[tableView scrollToRowAtIndexPath:nil
atScrollPosition:UITableViewScrollPositionNone
animated:NO];
];
I hope it helped.
Try this, I am not sure whether this will work or not.
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
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){
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
[self.messagesTableView endUpdates];
});
}
Note: You might require to update rowsInsertIndexPath reference to use inside the block (__block type).
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.