I am working in a project which is in Objective C and Xcode version is 10.1. In this project I am using socket. In this app I am continuously reading data from Socket. And I am showing the updated value into table view. For that I am reloading the table section header as fast as data comes from socket. Means If I get id = 5 then I try to find index of section whose id == 5 and I will update that section only.
Now the problem is when I tap on table header it opens inner row of that section header and after that when I scroll down, the table-view moving up and down like jumping continuously because data is coming in such a fast way.
For Ex. : Table View
Case 1 :(Scroll is working perfectly)
Section Header 1 (When I click on Header 1, Row 1 will appear)
Section Header 2
Section Header 3
Section Header 4
Section Header 5
Case 2 : (Scroll will lead to Jumping Headers)
Section Header 1 (When I click on Header 1, Row 1 will be removed)
Row 1
Section Header 2
Section Header 3
Section Header 4
Section Header 5
So in above structure, when we click on Header 1 and then we scroll down, at that time next headers are jumping because of continuous update process.
This jumping header effect in scroll, is not coming in Case 1 scenario. This happened only in Case 2.
This problem was not occurring when I was reloading the whole table instead of reloading the only one section header at a time.
Please help me in this issue.
Thanking you in advance.
I tried to reload section header like mentioned in below.
[tableView beginUpdates];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:i] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
If you are using "heightForRowAtIndexPath:" method to calculate the height. Use "estimatedHeightForRowAtIndexPath" this method also. Write code in row height method.
When you reload the section of the UITableView, it will try to do the whole render process again. That means, it will get the number of rows, number of sections, get the appropriate cell for that row or section, will calculate height of each one of them and then draw on screen.
This whole process takes a lot of time. Since all the UI operations happen on Main thread which is a serial thread, and you are updating the table view section continuously and very fast, this will cause a lot of jitter.
A better way to handle such a scenario is to delay the updates a little. Say in an interval of 1 min, take the latest update. This can be achieved using RxSwift or may be implement your own logic.
Then after you do that, an optimization that you can attempt is to update the section view yourself rather than letting UITableView doing it for you. This means, that based on the current state of the UITableView, find the section you want to update with the data, use indexPathsForVisibleRows and cellForRow(at indexPath: IndexPath) to get the cell and directly update the view.
This method will work fine if the new data does not change the height of the cell or section. Otherwise, you will have to invalidate the height of the cell which will introduce a little bit of jitter.
If you reload section, then everything to do with that section will be redrawn, including row numbers, header contents, and default section behaviour (closed or expanded). you need to change your design or your approach to the solution required - for example, make the table non-closable (ie header and childs are always visiblle), or other things.
Related
Hello i have a weird edge case on UItableview when a new section is inserted.
What I expect: I insert a new section at 0 and viewFor/willDisplayed is called for that newly inserted section only.
What Im getting: I get the desired behaviour in all cases were current section count is 2 > N. But if there is currently only one section in the table and i insert a new one. viewFor/willDisplayed will be called on both 0 and 1.
This severely messes up the ui animations as I on insert-completion, update the content of section 1 (without reloading it explicitly) and call beginUpdate/endUpdate to update its height. But in this edge case this will now be called after its already got its new model set without any animations though viewForHeader resulting in a glitchy animation.
Anyone have any insight on how to overcome this Feture/bug of uitableview?
The issue is: When I execute reloadSections, the last cell of one section will jump to the first cell of the next section, even though cellForRowAtPathIndex definetly does not load this cell there (thouogh I did check and it is dequeing that cell, then updating it), and there will be an empty white space (sometimes) where a cell should be in the previous section. If I run reloadData, my whole table goes white until I scroll aroud a bit at which point it sometimes works. Background information:
I have a table view, which is displaying data with several rows per section. In the backgroud, a download may finish, which may or may not update the number of rows in each section and the data contained in the rows. The rows are varying heights, which is expensive for a "reloadData" call, so I don't want to execute that everytime new data arrives (only one section may be updated).
So, what I do when I start displaying a cell is check if a download for that section has been completed, and if it has I then reload that section. If that section is updated, I check the other sections as well (Since it often downloads sections together), and update all those.
Okay, here is the issue. I run something like this (abbreviated for simplicity / at work don't have exact code with me):
[tableView beginUpdates];
for (int i = 0; i < numSections; i++)
{
if ([self sectionNeedsUpdate:i])
{
[self updateNumberOfRowsInSection:i]; //if Needed
[tableView reloadSections:[NSArray arrayWithObject:i] withRowAnimation:UITableViewRowAnimationNon];
}
}
[tableView endUpdates];
The idea is that I update each section individually, but all 5 may be updated in that loop. Or maybe just section 1 / 3, etc. Why would a cell just jump to the next section? Are those reloadSections calls running in their own thread and dequeing each other at the same time?
I'm trying to use this method to implement an infinitely scrolling UITableView
The core logic of the solution is:
To increase the tableview content by a factor of 3, so that we make the 3 copies of the content laid one after another vertically.
Whenever the top end of the scroll is reached, move the the scroll offset back to start of the 2nd copy
When the bottom end of the scroll is reached, we move the scroll offset back to the start of the 2nd copy minus the height of the tableview, so that we end up showing the same content as we are now.
This means that a single cell insert or delete actually results in three inserts or deletes. Because my table's datasource is populated by an NSFetchedResultsController it causes an Assertion Failure.
The number of rows contained in an existing section after the update (12) must be equal to the number of rows contained in that section before the update (15), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Is there some way to stop the program from crashing in these cases? I'd really appreciate any help/pointers. Thanks.
This can be kind of tricky in iOS especially when you're first starting out. The key is to update the datasource that is backing your UITableView before you inform the tableview of changes.
For instance, if you have an NSArray backing your tableview, then you will want to remove or add items to it, prior to calling reloadSection: on the tableview.
Although it is the least optimized solution, while your testing feel free to simply call reloadData which will ignore what the tableview has cached and force it to recalculate based on whatever is backing the tableview.
You can use NSFetchedResultsControllerDelegate method controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:. To insert\delete 3 objects.
To keep it simple you could return 3 for number of sections in your table view (this will handle getting 3 copies of objects). And then in your NSFetchedResultsControllerDelegate update objects in all sections of your UITableView (insert, delete or move). You should use row number from the NSFetchedResultsControllerDelegate and apply all changes 3 times each for different section. Remember to keep it consistent with UITableView data source method tableView:numberOfRowsInSection:.
I have a basic tableview with section headers that contains cells. A cell either has a checkmark visible or not. When the user selects the cell, it toggles the checkmark, and if all items in that section has the checkmark visible, additionally the section header is greyed out.
I trigger the UI update in tableView:didSelectRowAtIndexPath: by first updating the model and then call reloadSections:withRowAnimation:UITableViewRowAnimationAutomatic on the relevant section.
The problem is that when animating the change (duration about 0.3 s), the user cannot select another row until the animation is done. It is a common usage scenario, wanting to check a number of items in batch. I also absolutely want to animate the change.
The documentation states that one can configure a UIAnimation using UIViewAnimationOptionAllowUserInteraction to remedy this, but since I'm not using a manual UIAnimation but only calling UITableViewRowAnimationAutomatic, I don't know how to accomplish this.
Is there a way to allow the user to select (and initiate animations) on other cells while such an animation/reload is ongoing?
Note
It's tricky to get a custom section header to redraw/animate, the only way I managed to do it is by first reloading the affected section, and then immediately reloading the table – the latter to hide a flicker that appears otherwise.
If I only reload the single affected row, there is not animation on the section header – it just changes.
I ended up disregarding that the section header does not animate, the user experience is more severely affected by not being able to quickly toggle several items in the list.
I am using a UITableView to display the results of a series of calculations. When the user hits 'calculate', I add the latest result to the screen. When I add a new cell, the UITableViewCell object is added to an array (which is indexed by tableView:cellForRowAtIndexPath:), and then I use the following code to add this new row to what is displayed on the screen:
[thisView beginUpdates];
[thisView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationFade];
[thisView endUpdates];
This results in the new cell being displayed. However, I then want to immediately scroll the screen down so that the new cell is the lowermost cell on-screen. I use the following code:
[thisView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
This almost works great. However, the first time a cell is added and scrolled to, it appears onscreen only briefly before vanishing. The view scrolls down to the correct place, but the cell is not there. Scrolling the view by hand until this invisible new cell's position is offscreen, then back again, causes the cell to appear - after which it behaves normally. This only happens the first time a cell is added; subsequent cells don't have this problem. It also happens regardless of the combination of scrollToRowAtIndexPath and insertRowsAtIndexPath animation settings.
EDIT:
I've now started inserting cells at the second-to-last position of the table, rather than the end, and the problem still occurs - when first inserted, a cell is 'invisible' until it goes offscreen and comes back on again. What could be causing this, and how can I force the cell to be drawn as soon as it is added to the table?
You're having problems because your updating the table without updating the data model backing it. Tables don't actually know how many rows they have nor what cells to display. They depend on the datasource and the delegate to tell them these things. Your design expects the table itself to track them.
insertRowsAtIndexPaths: is intended to be used for moving existing rows around a table, not for adding entirely new logical rows. When you insert an entirely new cell, the tableview looses track of how many rows it actually has.
Before you display a new row, the first thing you should do is update the values returned by:
– numberOfSectionsInTableView:
– tableView:numberOfRowsInSection:
... to reflect the addition of the new rows. This will allow the table to understand how big it is.
Then you need to update cellForRowAtIndexPath: to return the correct cell for the added row. Then you need to reload the table.
After you've done that, you should be able to scroll the tableview to the end and have the cell display properly.
The important thing to remember about tables is that they are dumb. The table itself holds no data, doesn't know how many sections and rows it has or what order the rows and sections come in. All the logic about data, sections, rows, cells and cell contents comes from the datasource and/or the delegate. When you want to change a table, you actually change the datasource and/or the delegate and then the table will reflect those changes automatically.
Edit:
Upon rereading the parent, I see that your putting the actual UITableViewCell objects in your data array and that you have one cell for each row.
This is not how tableviews are supposed to work and this will not scale beyond a few dozen rows at most.
Tableviews are intended to be an illusion that allows you display a lOGICAL table which has an arbitrary high number or rows. To that end, it only keeps enough UITableViewCell objects alive to cover the visually displayed area in the UI. With a default cell height of 44 pixels this means a tableview will never have more than 9 cell objects at a time.
Instead of eating memory holding cells that are not displayed, the tableview lets the delegate dequeue a cell that has scrolled off screen, repopulate it with the data of another LOGICAL row and then display it in a new position. This is done in cellForRowAtIndexPath:
You really need to start over here with your design. Your data needs to be kept separate from the user interface objects. You don't want to have more cells alive at anyone time than absolutely necessary because your memory use will balloon and your response time will degrade. Your current problem is the result of this unusual design.
When you've done that, you can add the result row as outlined above.
Try to scroll with some time shift after cell update via NSTimer or performSelector:withDelay:. It can help but to fix all problems I think there need to do more work.
The glitches may be caused because a UITableView considers itself the owner of any UITableViewCell instances it is displaying, and reuses them as needed. Part of that process is calling prepareForReuse on the cell. Since you are keeping the cells in an array, you do not want them reused. Try implementing an empty prepareForReuse in your UITableViewCell class. Or just create cells dynamically in tableView:cellForRowAtIndexPath: as apple recommends.
I used what Skie suggested to avoid the problem in the following way:
Immediately after adding the row:
[self performSelector:#selector(scrollToDesiredArea:) withObject:newIndexPath afterDelay:0.4f];
This called the following:
-(void)scrollToDesiredArea:(NSIndexPath *)indexPath{
UITableView *thisView = (UITableView*)self.view;
[thisView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
The delay of 0.4s seems to be sufficient to avoid the glitching; any less and it still happens. It may have to be different on varying models of iPhone hardware, though - I only tested on emulator.