Behavior when inserting a row to a UITableView - ios

I am making a chat application. When the keyboard appears, my UITableView and toolbar (with textfield and button) move up using the setFrame method.
The problem occurs when I am receiving a message from another user while I'm typing a message. When that happens (i.e. after an insertRowsAtIndexPaths to the UITableView), my UITableView and toolbar "reset" to their original positions.
Is this normal behavior caused by the system? I can't find anything in my code that would do this. I want that the toolbar remains visible so I can still type my message.
This is the code to add a convoItem (conversation item) to my array and UITableView:
- (void)insertConvoItem:(ConvoItem *)item
{
[_convoItems addObject:item];
// Update the table view
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:([self.convoItems count] - 1) inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
// Scroll to the bottom so we focus on the latest message
NSUInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
if (numberOfRows) {
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:(numberOfRows - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}

It's normal. Receiving new message resets your table view to it's original position.

Related

Insert last row in UITableView with smooth scrolling

I am trying to create a chat bot like application, I have used a UITableView with custom cells to fit my needs. Whenever a new message is added, I create and insert a new row and then scroll to the bottom of the UITableView. Everything works fine till a certain point, but when the height of the cells change (I have two different type of cells), the animation is messy, it doesn't smoothly scroll to the end and the entire UITableView flashes, which is not a good user experience. I have tried a couple of approaches:
1 - Add the data to the data source array and reload the UITableView, then scroll to the bottom.
2 - Use insertRowsAtIndexPaths then scroll to the bottom.
Both of them have the same issue with scrolling. I have used scrollToRowAtIndexPath to get to the bottom of the UITableView
I have uploaded the code of a demo app that represents simulate the same issue here so it will be easy to understand. Here is a video of the issue. Any help is really appreciated.
This issue MAY NOT occur on a simulator, kindly run the demo project on a device.
After reading all the comments and having a discussion in chat, I noticed this is happening on the iPhone 5C (10.3.3). I ran the demo on an iPhone 5S (11.3) and the issue does not occur. Not sure if this has to do something with the OS.
Reloading whole tableview results in messy scrolling instead insert row at last position.
Make following changes in your Action Methods
- (IBAction)btnLargeCellClicked:(id)sender { // To add large green colored row.
/************ This is the FIRST approach. ***********/
[arrHeight addObject:#"100"];
[arrData addObject:#"1"];
NSIndexPath* ip = [NSIndexPath indexPathForRow:[_myTable numberOfRowsInSection:0] inSection:0];
[_myTable insertRowsAtIndexPaths:#[ip] withRowAnimation:UITableViewRowAnimationFade];
[self scrollTableviewToLastRow];
/************ This is the SECOND approach. ***********/
// [arrHeight addObject:#"100"];
// [_myTable beginUpdates];
// NSIndexPath *row1 = [NSIndexPath indexPathForRow:arrData.count inSection:0];
// [arrData insertObject:#"1" atIndex:arrData.count];
// [_myTable insertRowsAtIndexPaths:[NSArray arrayWithObjects:row1, nil] withRowAnimation:UITableViewRowAnimationFade];
// [_myTable endUpdates];
// [self scrollTableviewToLastRow];
}
And
- (IBAction)btnClicked:(id)sender { // To add small red colored row.
/************ This is the FIRST approach. ***********/
[arrHeight addObject:#"50"];
[arrData addObject:#"1"];
NSIndexPath* ip = [NSIndexPath indexPathForRow:[_myTable numberOfRowsInSection:0] inSection:0];
[_myTable insertRowsAtIndexPaths:#[ip] withRowAnimation:UITableViewRowAnimationFade];
[self scrollTableviewToLastRow];
/************ This is the SECOND approach. ***********/
// [arrHeight addObject:#"50"];
// [_myTable beginUpdates];
// NSIndexPath *row1 = [NSIndexPath indexPathForRow:arrData.count inSection:0];
// [arrData insertObject:#"1" atIndex:arrData.count];
// [_myTable insertRowsAtIndexPaths:[NSArray arrayWithObjects:row1, nil] withRowAnimation:UITableViewRowAnimationFade];
// [_myTable endUpdates];
// [self scrollTableviewToLastRow];
}
Hope this helps :)
After seeing your source, can say tableView reloadData reloads cells every time, that makes animation more busy every additional cell.
For your goals you should use another system:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
And than, maybe you will need scrollToRowAtIndexPath at the end too
You would need this, change second approach like below
[arrHeight addObject:#"50"];
// [_myTable beginUpdates];
NSIndexPath *row1 = [NSIndexPath indexPathForRow:arrData.count inSection:0];
[arrData insertObject:#"1" atIndex:arrData.count];
[_myTable insertRowsAtIndexPaths:[NSArray arrayWithObjects:row1, nil] withRowAnimation:UITableViewRowAnimationNone];
[_myTable reloadRowsAtIndexPaths:#[row1] withRowAnimation:UITableViewRowAnimationNone];
// [_myTable endUpdates];
[self scrollTableviewToLastRow];

Programmatically select UITableView cell not in current view

So it's easy to select a row that is currently in the UITableView. For example, to select the first row:
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionNone];
Say that I have an array that is the data source for the table and the array count is greater than the number of cells displayed in the tableview. How can I get the UITableView to scroll to an index from the array that is beyond what is currently being displayed in the tableview?
All I am trying to do is to replicate programmatically what a user would do with their index finger as they scroll down the table.
My specific table displays 9 rows. My array has 20+ items. As the UIViewController loads, it retrieves the row number that should be selected (from an integer stored in NSUserDefaults). But I find that it will only scroll to the correct array position if that integer value is between 0 and 8. If it is 9 or greater, nothing happens, and I can't figure out how to make it respond to this. I've looked at all the UITableViewDelegate methods and none seems to address this.
What I've been doing to scroll and select a specific row is this (example arbitrarily selecting row 11):
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:11 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:11 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionTop];
Can anyone help me? I assume it isn't difficult, but I'm stuck.
Thanks!
Your cells that are off the screen ain't selecting because you are using reusable cells. The cells from the visible screen will be used later, it isn't that all 100 cells are cached and each cell is responsible for each row. What it means is that they could or couldn't have something in it already. For example, lets say you have cell for row 1. When it comes off the screen, in the next few cells it will be reused as cell 15 or something, and if it had selected properties, it will still have it. It is like a new job and you get a desk from the developer before you - you could have desk with his trash, but it could also be clean.
I wouldn't select them as you select them by method, but in if statement in your cellForRowAtIndexPath. Something along the lines (added comments):
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
// When using method with forIndexPath you don't have to check for nil because you will always get cell
MyTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
MyObj *obj = [self.myArray objectAtIndex:indexPath.row];
cell.location.text = obj.location.location_description;
// other formatting, text display, image loading, etc.
if ([self.selectedObjects containsObject:obj]) {
// do some selecting stuff
} else {
// but don't forget to unselect because you can get already selected cell
}
return cell;
}
Edit: To select invisible cell, first scroll to it, then select:
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
Try using UITableViewScrollPositionBottom instead of UITableViewScrollPositionNone
That is use this code
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:10 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionBottom];
I figured this out. My code was running in viewDidLoad which is too early. I needed to move it to viewDidAppear. At least I know that I am not losing my mind.

How to add a row to a Table View controller (with data from a textfield) when a button is pressed [duplicate]

This question already has answers here:
How to insert new cell into UITableView in Swift
(5 answers)
Closed 7 years ago.
I am making an app that is using core data, and I have 2 textfields and a button saying "save data".So I want when the button is pressed the data from the textfields is displayed on a new row in a table view controller.In Xcode using Objective C.
Image of views - http://i.stack.imgur.com/7UrMO.png
Thanks
Beginner to programming
Follow these steps:
Step 1: Update your model (used to draw your cell) with textFields text. Something like this:
[self.items addObject:<Object_With_Both_TextFields_Text>]
Step 2: On button tap, insert a new row like this:
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:self.items.count - 1 inSection:0];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
Just add the new data from text fields to your table view data source (mostly this will be a mutable array or dictionary). Then call reloadData on your table view.
Add the entry from text fields into your table view data source.
[yourMutableArray addObject:[textField1 text]];
call [yourTableView reloadData];
When you press 'save data', add content that you want to display into the your_array that you are using to display the tableview.
[your_array addObject:your_textfield_string];
After that reload the table again using [tableview reloadData]:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[tableview reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
////TO SCROLL THE TABLE TO NEWLY ADDED ROW
int lastRowNumber = [tableview numberOfRowsInSection:0] - 1;
NSIndexPath* ip = [NSIndexPath indexPathForRow:lastRowNumber inSection:0];
[tableview scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:NO];
});
});

UITableViewRowAnimationNone - behaving as UITableViewRowAnimation(Top/Bottom)

I have set the row insertion with the following code. I am only using UITableViewRowAnimationNone when inserting and deleting the rows, but sometimes ,as you can see in the gif below, the row animates in from the top or bottom. For the most part it doesn't animate, as I want it, but sometimes it animates on insertion and deletion. I am not talking about the table view expanding to show the inserted cell, I mean the cell appears to be sliding in from the bottom or top.
Here is the method that controls the insertion animation:
- (void)contentHeaderFooterView:(NFContentHeaderFooterView *)headerFooterView sectionOpened:(NSInteger)section{
NSIndexPath *pathToAdd = [NSIndexPath indexPathForRow:0 inSection:section];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[pathToAdd] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
And here is the method that controls the deletion animation.
- (void)contentHeaderFooterView:(NFContentHeaderFooterView *)headerFooterView sectionClosed:(NSInteger)section{
NSIndexPath *pathToDelete = [NSIndexPath indexPathForRow:0 inSection:section];
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[pathToDelete] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
I've had similar issues with table updates not performing the animation I gave it directly. I can't say for sure why, but one thing I can suggest is, instead of doing a specific row deletion between begin/endUpdates, you can just to a hard reloadData.
Answered here: https://stackoverflow.com/a/37352789/577237
Reposted since it's brief:
[UIView performWithoutAnimation:^{
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:rowsToRemove withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}];
Note that this is iOS 7.0+

Row deletion does not refresh table view in ios app

I have spent hours searching for the solution with out any luck. I am trying to delete a row (also deselect same row) programmatically. After row deletion call below, UITableViewDelgate methods get called expectedly and data source is updated but UITableView is not refreshed. deselectRowAtIndexPath call also does not work. I tried all kinds of scenarios as shown by commented lines.
Here is my code:
checkoutPerson is called as a result of observer listening for NSNotificationCenter messages.
- (void) checkoutPerson: (NSNumber*) personId {
Person *person = [_people objectForKey:personId];
if( person )
{
// Remove person from data source
int rowIndex = person.rowIndex;
S2Log(#"Deleting row number=%d", rowIndex);
[_allKeys removeObjectAtIndex:rowIndex];
[_people removeObjectForKey: personId];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
//[[self tableView] beginUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
S2Log(#"Deleting indexPath row=%d", [indexPath row]);
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
//[[self tableView] endUpdates];
S2Log(#"Reloading data");
//[[self tableView] reloadData];
//[self performSelector:#selector(refreshView) withObject:nil afterDelay:1.5];
//[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
}
I will appreciate for help.
Thanks
-Virendra
I believe deleted cell is not being recycled. If I delete row in the middle, last row is always erased (since there is one less item) but the deleted row remains.
Use the above code between two function for table view
[tableView beginUpdates];
// the deletion code from data source and UITableView
[tableView endUpdates];
By calling this functions you are telling UITableView that you are about to make updates for deleting your cell.
Edit
The other problem I see with your code is you first delete the data from the data source.
Now you are asking for the UITableViewCell (which actually reloads the UITableView)
and then you are deleting the row from UITableView
I guess you should fetch the UITableViewCell before deleting values from your data source.
I found the problem. It has nothing to do with the code I posted above. It is syncing problem between visual display and the contents of data source. I have an embedded UITableView as part of a composite view. In composite view's controller, I was wiring up UITableView's delegate and data source to an instance of UITableViewController. Instead of this, I should have set UITableViewController's tableView property to the embedded UITableView. It seems that UITableView has to be contained within UITableViewController in order to correctly sync up table view visual display to the contents of data source. This also fixes row deselection and scrolling. I also needed to delay reloadData call in which case deleteRowsAtIndexPaths:withRowAnimation is not required. All you need is to modify the contents of your data source and call reloadData with a delay of 1.5 Seconds.
Thanks to all for great help.

Resources