I have a problem with UITableView.
I have a ringbuffer with maximum 20 element.
I add this buffer as source to a tableview.
When i add a item i call
table.BeginUpdate();
tableMessges.InsertRows(new NSIndexPath[] { NSIndexPath.FromRowSection(buffer.Count-1, 0) }, UITableViewRowAnimation.None);
buffer.Add(item);
tableMessges.EndUpdates();
This work fine.
Now when the buffer is full, i delete the first table row (also the first object in buffer), insert a new row and add a new object to buffer.
When i do this procedure slow (every second) it look fine.
I just want to have maximum 20 items in the table.
BUT when i call this procedure lets say ever 100 ms the the table view flashes a lot.
Is there any chance to reduce the flashing.
here is my code when the buffer is full.
table.BeginUpdates();
buffer.AddMessage(canMessage);
if(buffer.count >= buffer.capacity)
{
table.InsertRows(new NSIndexPath[] { NSIndexPath.FromRowSection(buffer.count-1, 0) }, UITableViewRowAnimation.None);
table.DeleteRows(new NSIndexPath[] { NSIndexPath.FromRowSection(0, 0) }, UITableViewRowAnimation.None);
}
else
{
table.InsertRows(new NSIndexPath[] { NSIndexPath.FromRowSection(buffer.NumberOfRows-1, 0) }, UITableViewRowAnimation.None);
}
table.EndUpdates();
I would suggest to instead inserting and deleting row directly to UITableView control, you should manipulate the DataSource binds to it and reload table data.
For optimize and specific data reload, you can look at the Apple Doc (Reloading the TableView) for UITableView control. For instance:
reloadRowsAtIndexPaths:withRowAnimation:
From your in-hand code, it looks you have certain details to go with this method.
Related
I am developing a chat conversation UI using UICollectionViewController, initially having 20 items in collectionViewController.
When I did scroll to top.
if scrollView.contentOffset.y == - 44 { /* Inserting 10 items at zero th position in collectionView */ }
I will insert 10 items at IndexPath(item: 0, section: 0)
So will have a total of 30 items in CollectionView.
At this time I got some UI animation issue and collection view was scrolled to top.
e.g actual items are 0,1,2,3...19
now I am inserting at zeroth index so items like 29,28,27,...20,0,1,2,3...19
I hope you understand.
my issue is when my collection view is showing 0,1,2,3 at top I am inserting 29,28,27,...20
but collection view won't scroll to up or down. It has to show same 0,1,2,3 like (silently insert items at top and don't change any ui)
Like WhatsApp chat - fetching old messages while scrolling to top.
I don't know if I understand, but you can try this:
self.collectionView.setContentOffset(CGPoint(x:0,y:0), animated: true)
after your insertItem method populate your datasource.
How could I load more data while scrolling to the top without losing the current offset oh the UITableView?
Here is what I am trying to achieve:
This is the whole set of data:
row 1
row 2
row 3
row 4
row 5
row 6
row 7
row 8*
row 9
row 10
row 11
row 12
row 13
row 14
row 15
Now, imagine that the user loaded the ones marked in bold and the offset is at row 8, if the user scroll up and reached row 7, I want to load and insert the rows from 1 to 5 without jumping from row 7. Keeping in mind that the user may be scrolling so when data reached the phone it is at row 6, so I can't jump it back to row 7, but keep the scroll smooth and natural (just how happen when you load more data while scrolling down, that the data is reloaded without the tableview jumping from between rows).
By the way, by offset I mean the contentOffset property of the UITableView.
Thanks for your help, I do really appreciate it!
When you are updating your data, you need to get the current offset of the tableView first. Then, you can add the height of the added data to the offset and set the tableView's offset like so:
func updateWithContentOffsset(data: [String]) {
guard let tableView = tableView else {
return
}
let currentOffset = tableView.contentOffset
let yOffset = CGFloat(data.count) * tableView.rowHeight // MAKE SURE YOU SET THE ROW HEIGHT OTHERWISE IT WILL BE ZERO!!!
let newOffset = CGPoint(x: currentOffset.x, y: currentOffset.y + yOffset)
tableView.reloadData()
tableView.setContentOffset(newOffset, animated: false)
}
You can also take a look at the gist that I created. Simply open up a new iOS Playground and copy-paste the gist.
The only thing you have to be aware of is that make sure you know your row height to add to the offset.
#Pratik Patel Bellow answer is best one.
Right down or call bellow function in the web-service response.
func updateWithScroll(data: [String]) {
guard let tableView = tableView else {
return
}
guard let currentVisibleIndexPaths = tableView.indexPathsForVisibleRows else {
return
}
var updatedVisibleIndexPaths = [IndexPath]()
for indexPath in currentVisibleIndexPaths {
let newIndexPath = IndexPath(row: indexPath.row + data.count, section: indexPath.section)
updatedVisibleIndexPaths.append(newIndexPath)
}
tableView.reloadData()
tableView.scrollToRow(at: updatedVisibleIndexPaths[0], at: .top, animated: false)
}
Now, imagine that the user loaded the ones marked in bold and the offset is at row 8, if the user scroll up and reached row 7, I want to load and insert the rows from 1 to 5 without jumping from row 7.
Loading data into the table as its needed is one of the things that UITableView does for you automatically. Don't try to insert rows into the table because they'll soon be needed as the user scrolls toward them -- the table view will request those rows from its data source as they're needed. You just need to make sure that your data source has the information it needs in order to fulfill the tables requests for cells as they arrive.
Things get a little more complicated if populating the rows requires making a network request for each row. Given your use of "load" in the question, I don't think that's what you're talking about, but in case that's your situation, here are some tips:
Request the data for as many rows as you reasonable can as early as you reasonably can. The amount of data displayed in a single row is typically small, so requesting a few hundred rows all at once shouldn't be a big deal.
If you don't have the data you need for a given row, make the necessary request, but return a cell immediately. The cell you return could use a spinner or other indication that the data is pending. When the request completes, you can tell the table to reload the appropriate row so that the proper content will display.
I have a PrimeFaces DataTable where I have pagination (the component one, no custom) in form of two arrows (back and forward).
The table never knows how many rows are there, so I have the following function to set the rows count (keep in mind that the dataSize parameter is for a pageSize of 25 rows always 25 or less):
if(pageNumber == 0 && dataSize < pageSize) {
setRowCount(dataSize);
} else if(pageNumber != 0 && dataSize < pageSize) {
setRowCount(pageNumber*pageSize + dataSize);
} else {
setRowCount(pageSize*(pageNumber+1) + 1);
}
With this function called in the load method after fetching the data, there are always one page or two pages or three pages. Depending on the current position of the first record the back arrow or the forward arrow are disabled or enabled. This all works well so far.
Now I want to be able to load the table on a given page. If I have a pageNumber the correct set of data is displayed but only the forward arrow is enabled. So the DataTable thinks that it is on the first page. I tried to call the setRowIndex() before or after my function but no luck.
Does anybody know how to make DataTable think that there is also a page before the current one?
I have a table view containing a variable amount of sections and custom cells. On some occasions, a cell may resize within RowSelected(). Whenever that happens, I'd like to also make sure the cell is completely visible after resizing (enlarging) it.
I have that working in another table that just modifies the underlying data so that the table view source will provide a larger cell. I then reload the cell and scroll it visible like so:
// Modify data
//...
// Reload cell
tableView.ReloadRows(new NSIndexPath[] { indexPath }, UITableViewRowAnimation.None);
tableView.ScrollRectToVisible(tableView.CellAt(indexPath).Frame, true);
The problem arises in a table view where resizing may not only be triggered by RowSelected(), but also by events on UI elements within the cells.
The events then call a method to reload the cell:
void updateCell() {
if (cell.Superview != null) {
UITableView tableView = (UITableView)cell.Superview;
tableView.ReloadRows(new NSIndexPath[] { indexPath }, UITableViewRowAnimation.None);
// Get the new (possibly enlarged) frame
RectangleF frame = tableView.CellAt(indexPath).Frame;
Console.WriteLine("This really is the new large frame, height: {0}", frame.Height);
// Try to scroll it visible
tableView.ScrollRectToVisible(frame, true);
}
}
This scrolls fine for all cells but the bottom-most. It only makes the old frame of that cell visible. I double-checked that it really provides the new cell frame to ScrollRectToVisible().
So it seems ScrollRectToVisible() is bound to the old content size of the table - even after reloading rows. I tried to work around that by providing a new content size with the calculated difference in height. That does work but feels really hackish to me.
Is there some cleaner way to do things?
Thanks in advance
Instead using this:
tableView.ScrollRectToVisible(_: animated: )
Use it to scroll UITableView to bottom row:
tableView.scrollToRow(at: at: animated: )
I have a table of many rows in a JQuery UI accordion.
I dynamically append the table this way:
var resJson = JSON.parse(connector.process(JSON.stringify(reqJson)));
for ( var i in resJson.entryArrayM) {
// test if entry has already been displayed
if ($("#resultTr_" + resJson.entryArrayM[i].id) == null)
continue;
$("#resultTable > tbody:last").append(listEntry.buildEntryRow(resJson.entryArrayM[i]));
}
Firstly I check if a row of the same tr id already exists. If not, I would append to the last row of the table.
It works. But the problem is: every time a row is appended, the accordion would scroll to the first row of the table. Since the table is remarkably long, it makes users inconvenient to scroll down again and again to watch newly-added rows. So how to avoid this?
First of all, just do one append rather than appending every time through the loop:
var resJson = JSON.parse(connector.process(JSON.stringify(reqJson)));
var seen = { };
var rows = [ ];
var trId = null;
for(var i in resJson.entryArrayM) {
// test if entry has already been displayed
var trId = 'resultTr_' + resJson.entryArrayM[i].id;
if($('#' + trId).length != 0
|| seen[trId])
continue;
rows.push(listEntry.buildEntryRow(resJson.entryArrayM[i]));
seen[trId] = true;
}
$("#resultTable > tbody:last").append(rows.join(''));
Also note that I corrected your existence test, $(x) returns an empty object when x doesn't match anything, not null. Not only is this a lot more efficient but you'll only have one scroll position change to deal with.
Solving your scrolling issue is fairly simple: find out what element is scrolling, store its scrollTop before your append, and reset its scrollTop after the append:
var $el = $('#whatever-is-scrolling');
var scrollTop = $el[0].scrollTop;
$("#resultTable > tbody:last").append(rows.join('')); // As above.
$el[0].scrollTop = scrollTop;
There might be a slight visible flicker but hopefully that will be lost in the noise of altering the table.
You could also try setting the table-layout CSS property of the <table> to fixed. That will keep the table from trying to resize its width or the width of its columns and that might stop the scrolling behavior that you're seeing. The downside is that you'll have to handle the column sizing yourself. But, you could try setting table-layout:fixed immediately before your append operation to minimize the hassle.