UITableView load more data when scrolling to top? - ios

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.

Related

Issue while inserting items in zero th index on UICollectionViewController with inputAccessoryView

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.

Reordering sections in UITableView

I have UITableView with sections and rows(https://imgur.com/a/hrYTEVR). I know how enable reordering for rows, but i dont know how implement reordering for sections.
I need add reordering control to sections(https://imgur.com/a/V5kU9Ew) and then when user touch this control, rows under section should flops. After that user can move section to new place.
After read topics on stackoverflow and other sites, i dont find any good idea how implement something like this.
I thought about implement sections through cells, but in this case i can't flop rows under section for further moving to new place.
If you have any idea how implement this – give me advice. Thanks!
There is no native functionality to achieve what you want. If I understand correctly you would want to collapse a whole section of rows and then start dragging the "header" around. If you want to do this on your own I would suggest starting with a pan gesture recognizer which triggers on the header button.
The gesture should be relatively obvious. After it starts on the header you need to track position using locationIn in your table view.
To collapse rows all you need to do is modify your table view cells with appropriate animation like:
tableView.beginUpdates()
tableView.deleteSections([myIndexPath], with: .top) // Maybe experiment with animation type
// Modify whatever you need to correspond this change in the data source
tableView.endUpdates()
Since you will be removing the section you will also be removing the view (header) which has the gesture recognizer. That means it might be better adding the gesture to the table view directly or its superview even. You will need to force it to trigger only when one of those buttons on headers is pressed. You can get some idea here about it. The rest is unaffected by this change.
At this point you will probably need to create an extra view which represents your section stack and follows your finger. This should be pretty easy if you add it as a subview and manipulate it's center with pan gesture recognizer locationIn in it's superview:
movableSectionView.center = panGestureRecognizer.location(in: movableSectionView.superview!)
So up to this point you should be able to grab a section which collapses all cells and be able to drag the "section stack" view around. Now you need to check where in table view your finger is to know where to drop the section. This is a bit painful but can be done with visibleCells and tableView.indexPath(for: ):
func indexPathForGestureRecognizer(_ recognizer: UIGestureRecognizer) -> IndexPath {
let coordinateView: UIView = tableView.superview! // This can actually be pretty much anything as long as it is in hierarchy
let y = recognizer.location(in: coordinateView).y
if let hitCell = tableView.visibleCells.first(where: { cell in
let frameInCoordinateView = cell.convert(cell.bounds, to: coordinateView)
return frameInCoordinateView.minY >= y && frameInCoordinateView.maxY <= y
}) {
// We have the cell at which the finger is. Retrieve the index path
return tableView.indexPath(for: hitCell) ?? IndexPath(row: 0, section: 0) // This should always succeed but just in case
} else {
// We may be out of bounds. That may be either too high which means above the table view otherwise too low
if recognizer.location(in: tableView).y < 0.0 {
return IndexPath(row: 0, section: 0)
} else {
guard tableView.numberOfSections > 0 else {
return IndexPath(row: 0, section: 0) // Nothing in the table view at all
}
let section = tableView.numberOfSections-1
return IndexPath(row: tableView.numberOfRows(inSection: section), section: section)
}
}
}
Once the gesture recognizer ends you can use this method to get the section you are dropping your items into. So just:
tableView.beginUpdates()
// Modify whatever you need to correspond this change in the data source
tableView.insertSections([indexPathForGestureRecognizer(panGestureRecognizer).section], with: .bottom)
tableView.endUpdates()
This should basically be enough for reordering but you might want to show in table view where the dragged section is. Like having a placeholder at the end of the section in which the stack will be dropped into. That should be relatively easy by simply adding and then moving an extra placeholder cell reusing indexPathForGestureRecognizer to get a position for it.
Have fun.

UI TableView Add rows at the beginning of the table view i.e at position -1

I have UITableview which has number of items like this
item4
item3
item2
item1
So when the user scrolls scrolls past item4 to the top i want to use func refresh to get item5, item6 and item 7 from core data and insert them to the top of the table view to look like
item7
item6
item5
item4
..
item1
When i do this i want to keep the scroll at item 4.
When i tried doing this and reload the table and scroll to to indexpath of item 4 user can easily notice this as some weird scroll happening. For scrolling to index path i have set the animation bool to false.
So
1.Is there a way to add items at index positions -1, -2 and so on or reloading the entire table to only option
OR
2.Is there a way to hold the scroll position even after the table reload (I can identify the last row shown by itemId and get the indexpath of the item while reloading and scroll to that index path but this results in a weird scroll effect even after setting the animation bool to .none)
P.S :I am using swift 3
Thanks for any help in advance
This worked for me.
In your get_Data_From_Server method:
arrayData.insert(your_data, at: 0)
self.tableView.reloadData()
self.tableView.contentOffset.y += calculated_size_of_the_cell_you_inserted
You'll see the row insertion and scrolling if the table does not have enough number of cells filling its entire view.
Once the table's view is filled with cells, new cell will be inserted at the top and table will hold its scroll position.

How to implement clean paging of collectionview where it scrolls to full page

I have a collectionview with a grid of cells that is 3 wide by 3 tall. I have paging enabled to scroll horizontally to see the next page of 9 cells. However, I don't have a full 9 cells for the last page, only 3 remaining cells. The paging only scrolls to the first column of cells and it looks bad. I want it to scroll fully to the third page so it displays the 3 remaining cells in the first column and and 6 empty ones in the second 2 columns. Any help is greatly appreciated! Here is the paging code I have, thanks in advance!
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
// sets which page we are currently on
let pageWidth:CGFloat = collectionView.frame.size.width
let currentPage:CGFloat = collectionView.contentOffset.x / pageWidth
// sets the page control to the current page
pageControl.currentPage = Int(currentPage)
}
When you get the Collection that would be displayed in your CollectionView you should do a modulo on it by 9 to get the remainder. If the modulo is 0, do nothing. If the modulo is any other number, subtract that number from 9 and add that many "dummy" cells to your collection to fill them out.
Here is some code to get you started:
int remainder = (int)[self.collectionView numberOfItemsInSection:0] % 9; //I am assuming you don't have more than one section
if(remainder){
for(i = 9; i > remainder; i--){
//add a dummy item to collectionView
}
}

ScrollToRowAtIndexPath takes longer to scroll because of UITableViewAutomaticDimension

Im trying to build a scroll within 977 strings of data in the array placed in UITableView , well I'm using the following to do automatic calculation for the
cell height : mytableView.rowHeight = UITableViewAutomaticDimension.
And using the following to scroll to index:
self.mytableView.scrollToRowAtIndexPath(NSIndexPath(forItem: placetogo, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
Whats happening here is the scroll is taking 16 seconds to scroll to that last index of the array which will be very annoying for the user, when i made the rowHeight = 400 or 300 static value , then it scroll with less than a second to last index.
My cells are different in there heights, so i cannot use the static cell height. I've made a research about it that tableview calls rowHeight frequently to calculate the height.which makes the scroll very slow, is there is any efficient way for that ?
I tried to split my data into 9 or 10 sections but still the same!

Resources