I've been searching a lot, but I don't find a clear way to create an Scroll to top load more, or infinite scroll, just like you see on chats, e.g whatsapp.
I was trying using scrollViewDidScroll method, load data again based on current page
if self.tableView.contentOffset.y == 0 {
loadData(++currentPage, itemsPerPage: pageSize)
}
But the way to handle new data and scroll position it's a little weird for me, so the main problem is adding content in an efficient way, at the top of my UITableView
//Update
In ScrollViewDidScroll method:
After contentOffset.y reaches 0 position, I'm trying to prepend new messages, since it must appear at top of UITableView, so inserting messages at index 0 is the only way I find to do this. But there is one thing, after pulling new data, and reloading UITableView, scroll position remains displaying at visible screen, last pulled message on top. This is not the expected behaviour, look this slack channel pic
when scroll reached proper offset, new data was pulled, and screen it's on the same message, but looks now the scroll position
so, I think reloading entire table view in this case is the problem, but how could you pull the data in the tableView properly to get this expected scroll behaviour?
e.g in whatsapp it's the same
I recommend make prefetching in UITableView delegate's method
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
//your code ()
if indexPath.row == datasource.count - 3 {
loadMore()
}
}
This allows you to avoid weird behaviour of scroll view.
Related
I have a UITableView with cells that I want the user to be able to reorder by dragging them up & down, using the "handles" on the right side of each cell. I do NOT want to implement "drag & drop" functionality, because the data behind these cells makes no sense outside of this table (and implementing NSItemProvider looks to be an ugly process, given the data behind the table view & its cells). So far, everything is implemented & works fine. Looks somewhat like the results of this article.
The next step is, I'd like to know when the user has "lifted" or started to move a cell -- similar to the dragStateDidChange ability when one IS using drag & drop. I want to get this notification even before the cell has been moved out of its place -- as the UI turns the cell background white & "raises" it above the table.
How can I get this notification, without implementing "drag & drop"?
Things I've tried that don't accomplish what I want:
Recognizing a long-press gesture: I do use Gesture Recognizers on these cells for taps & such; it seems that when the user touches the drag handles to reorder the cells, this action does NOT fire a long-press Gesture. Tried that, no success.
Will-select-row-at: I implement the function func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? within the UITableViewDelegate code; it isn't called when the user touches the drag handles.
targetIndexPathForMoveFromRowAt: The func func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath fires when the dragged cell changes position, but not until then; it does NOT catch the start of the lift/drag process that I'm looking for.
Likewise, willBeginEditingRowAt and shouldHighlightRowAt aren't called either.
Various scrollView functions: they all get called when the user is scrolling the whole table, not when the user is reordering it.
Watching for "set editing": the drag handles need to always be present, so "editing" is always set to true.
Somewhat similar question, unfortunately without an answer that I can use (and also not in Swift).
I suspect that there must be a simple notification that the cell has been lifted and is being dragged using the handles, even if it hasn't been moved far enough to be above or below its old place yet. After all, the OS "knows" about this because it changes the background and adds shadows & so forth. Any help accessing that notification so that other code can respond to it as well would be most appreciated!
It's a little strange that these are not documented, but you can define the following functions in your UITableViewController (or in the UITableViewDelegate) which will be called when cell dragging starts or ends, respectively:
#objc func tableView(_ tableView: UITableView, willBeginReorderingRowAtIndexPath indexPath: IndexPath) {
// Dragging started
}
#objc func tableView(_ tableView: UITableView, didEndReorderingRowAtIndexPath indexPath: IndexPath) {
// Dragging ended
}
I have a "Create Tribe" "button" in the header of a UITableView. It's not an UIButton, but 3 views ("+" icon, label, and a background view)
I have added a tap gesture recognizer to the background of the button (CreateTribeButton layer in the hierarchy).
The problem is, tapping on the button only works 1/3 of the time. I have to tap a few times in order for it to trigger the desired action.
I have no idea where I should begin debugging.
This view controller is embed in a pager - https://github.com/xmartlabs/XLPagerTabStrip
I have tried disabling the canCancelContentTouches and delaysContentTouches in the table view (which is actually a scroll view), and its parent pager's content view (which is also a scroll view). It doesn't actually solve the problem.
Is there an easy way for me to find out on which view did the touch get "eaten" up?
The easiest way to tell which section was tapped is in the
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){}
Function. You could simply implement
print(indexPath.row)
to find out what section was tapped. I would put a break point on this function and step through it. Additionally, you could programmatically call the buttons functionality when the right row is pressed. For example:
switch indexPath.row {
case 0:
doButton()
case 1:
/// do something else
default:
// break
}
That will help you figure out what is going on.
I have a UITableView which cells are custom, and cell has a view with chart (Charts library). Data for chart is pretty big (it stored as a property of Controller), it appears during few seconds.
Also I do some Chart's View setup (only little UI changes) in setSelected method in custom TableViewCell class
When scrolling table view there some lags appear. I suppose it happens because of heavy content I want to display.
During solving this problem I've thought about four solutions:
Load all cells in ViewDidAppear, save them to array, in CellForRowAt method show cells from this array.
Put table view into scroll view, make content's height pretty big to fit all cells in table view.
Make chart loads only after cell's loading and its content view appearing. Show activity indicator while chart's loading is on.
Put chart's loading in background thread.
First two approaches seem to me a not so good in terms of memory managment. But I am not sure, maybe there is common solution which I have not known.
Some code example from project:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let graphCell = tableView.dequeueReusableCell(withIdentifier: Cells.graphViewCell) as! GraphViewCell
graphCell.lineChartView.data = track.chartDataForHeight()
return graphCell
}
Here's track is variable where I store data for chart.
If you have a very complex cell and not too many of them and you do not want to pay to create it every time, then its correct not to use dequeResuableCell and just lazily create the cells and cache them. You can mitigate the memory problem by responding to didReceiveMemoryWarning and dumping the off screen cells then lazily recreating them. UICollectionView now has prefetching in ios 10. You can also try using this instead of UITableView.
I have a UITableView with prototype cells. If you swipe any cell to their left, two options are displayed on the cell correctly. This has been done with
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
Now, I want to also allow users to delete a cell. I understand the best way would be to make a complete on a cell swipe in order to remove it. This means, if there swipe is not complete, options will be displayed (as it is done now), but if the swipe is complete, the cell would be removed.
What code should I add to keep the actual behavior, but also enable to remove a cell by swiping it completely?
This repo has what you want. Have a look.
https://github.com/MortimerGoro/MGSwipeTableCell
If I create a UICollectionViewCell subclass like:
class StudentCell: UICollectionViewCell {
var student: Student?
}
And in my controller I implement UICollectionView's didSelectItemAtIndexPath and set the variable:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let studentCell = collectionView.cellForItemAtIndexPath(indexPath) as? StudentCell {
studentCell.student = self.someStudent
}
}
When I click on the cell it should set student, but if the cell is scrolled off screen it seems like the whole cell might get wiped and when it comes back on screen it would need to rebuild itself in cellForItemAtIndexPath.
The problem I have is that I have a UICollectionView with cells that do not scroll off the screen that I'm storing data in. Because they don't leave the screen I assume they should not get wiped, but one of the cells does not seem to keep it's variable, which makes me think maybe it's getting wiped and I may need to move the state outside of the cells to a dictionary or something and in didSelectItemAtIndexPath instead of setting the variable in the cell I'd set the dictionary. Whenever I need to access the data instead of asking the cell for it I'd look it up in the dictionary.
But either way, I was wondering if it's possible (or a bad idea) to set it in the cell or not.
Yes, cells in both UICollectionView and UITableView can (will) be reused at the systems discretion and should not be used to store state information, only display information. Specifically, both views will reuse cells when they are scrolled off-screen, but there's no guarantee this is the only time they'll be reused. The usual way to handle this is to define some kind of cell data object which stores the data for each cell (visible and not) and refresh the cell view from that as needed/requested.
Tables display their data in cells. A cell is related to a row but it’s not exactly the same. A cell is a view that shows a row of data that happens to be visible at that moment. If your table can show 10 rows at a time on the screen, then it only has10 cells, even though there may be hundreds of rows with actual data. Whenever a row scrolls off the screen and becomes invisible, its cell will be re-used for a new row that scrolls into the screen.