I am trying to move a UITableViewCell to the absolute top of the table (IndexPath 0,0). I have tried this:
let conversation = conversations[indexPath.row]
conversations.remove(at: indexPath.row)
conversations.insert(conversation, at: 0)
conversationsView.moveRow(at: indexPath, to: IndexPath(row: 0, section: 0))
This does what I want it to do: it moves the cell (currently in didSelectItemAt method for test purposes) to the top. However, it doesn't happen visually as I want it to happen.
What happens now is, the cell moves out to the bottom, and gets inserted from the top. What I'd like is some sort of animation that "moves" the cell from the current position (e.g. IndexPath 4,0 to the new position (IndexPath 0,0).
A perfect and accurate example of this is Facebook Messenger. When you are in the overview of all your conversations, and you receive a message in the third conversation, the cell moves "magically" and "visually" as you'd expect. This behavior I want to replicate.
Might be important: I use TextureGroup (AsyncDisplayKit) in my application. But this should behave like UITableView under the hood I guess.
Related
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.
I have a UICollectionView and each cell inside it represents a message in a chat-like view. I set the top content offset everytime a new cell is added so that the cells "stick" to the bottom of the UICollectionView and new ones push the cells up the screen like this:
------------
|-----
| // This space represents a content offset from the top
|-----
A
B
C
D // Each of these is a UICollectionViewCell
------------
This works fine. However, after 8 seconds I remove the UICollectionViewCell at the top of the UICollectionView. This makes the remaining cells move up to fill its place and then I set the content offset to accommodate for the lost cell and they all move back down to act as if they're stuck to the bottom of the UICollectionView. How can I make it so that the removal of the top UICollectionViewCell doesn't make the rest of the cells jump up and down like this?
Don't use contentOffset property to manipulate the top offset. In UICollectionView all the properties like contentOffset, contentSize, etc. are managed by UICollectionViewLayout, which can be custom to match your specific needs. As for me, there are two options (by priority):
1) Look for a custom layout (or implement one by yourself) which would let you achieve the desired chat-like behavior
2) Insert a dummy empty cell at the top of your collectionView and every time you delete/add any new cells, manage the dummy one's height to achieve the goal by invalidating its layout or just calling collectionView.reloadItems([IndexPath(row: 0, section: 0)])
I solved this by removing the need for contentOffset and using the following steps:
Perform 180 degree rotation of UICollectionView: collectionView.transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: 0)
Apply the transform on the contentView of each cell in the collectionView: cell.contentView.transform = collectionView.transform
Reverse data array used in collectionView: myData.reversed()
I am facing issue with UITableView in Swift. Table contains lyrics. Each cell is a lyric class. I know which index I have to focus on so I show the current lyric in red and the rest in black.
All okay so far. But when using scrollToRow, it's working well before table starts to scroll down to lyrics outside the view. Once the UITableView starts scrolling outside the view, it starts jumping.
Any idea how to make it scroll smoothly to the current cell without jumping?
Here is how I make the table go to the desired lyric:
let lcCell = IndexPath(row: (vc_lyrics.sharedInstance().lyric_time[seconds]?.index)!, section: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
vc_lyrics.sharedInstance().tableView.scrollToRow(at: lcCell, at: .none , animated: false)
vc_lyrics.sharedInstance().tableView!.reloadData()
}
Or if I can keep the lyric cell in the center by scrolling the scrollview of the table, not its delegate scrollToRow.
Change this line to set animated to true:
vc_lyrics.sharedInstance().tableView.scrollToRow(at: lcCell, at: .none , animated: true)
This will cause the cells to scroll smoothly. I would also think about whether or not you need to reload the tableView data here because that can also cause scrolling issues as #rmaddy mentioned.
Is there any chance to scroll in the UICollectionView to the wanted item using . scrollToItemAtIndexPath and snap not to the item itself, but to the page the item is part of? (I got paging enabled.)
Cheers
You need to create NSIndexPath than scroll to that index.
//Mark: - Move to cell when view did appear
overload viewDidAppear() {
let scrollIndex: NSIndexPath = NSIndexPath(forItem: dayIndex, inSection: monthSection)
if (// Check you are in range of UIcollectionView's count) {
//Scroll collectionview according to your requirement i.e UICollectionViewScrollPositionCenteredHorizontally or UICollectionViewScrollPosition.Left
self.YourCollectionView.scrollToItemAtIndexPath(scrollIndex, atScrollPosition: UICollectionViewScrollPositionCenteredHorizontally, animated: true)
}
}
I did end up using offsetContent property;
I knew the width of every so called page.
I wrote a code to get the indexPath for current day
I retrieved the section of the indexPath
I multiplied the section by the width of the view and named it "offset"
I set my UICollectionView.contentOffset.x to "offset" and it works fine.
I also tried using .scrollRectToVisible and the offset, and it worked amazingly, but I also wanted to updated something that was based on the contentOffset of the UICollectionView, and the .scrollRectToVisible seems not to update this property - it was updated only after I dragged the view a little.
Thanks for all your help!
Estimated row heights with UITableView are great for performance, but boy are they a headache when trying to have an accurate scroll offset with your table view.
This article from Ray Wenderlich details some of the issues at the bottom. As does this article. When you go to a new view controller and then return back to the table view after, the table view gets completely confused where you are and doesn't return the user to their previous location. The same can occur with rotation sometimes and even simple scrolling.
I can't help but wonder... what is the point then? Should it not bring you back to exactly where you were before?
I know that's difficult with estimates, but is there no way to accomplish that other than giving perfect estimates (which then aren't really estimates and defeat the purpose)? Because right now unless there's a single view controller with cells in it, any other situation, such as segueing to a new view controller seems to really mess it up.
How do I use estimates in a way that is actually useful and doesn't cause jumping all over the place?
I had exactly the same problem as you - a UITableView with different row heights embedded within a UIPageViewController. The TableView would scroll fine, I would swipe away and back onto the table, and the position would be all over the place.
Using both estimatedHeightForRowAtIndexPath and heightForRowAtIndexPath is what did the trick for me, as indicated in this article. From then on, whenever I swiped away and back onto the table, the position would remain the same. Hurray!
There was still a problem though: the first time the table is loaded estimatedRowHeights only gets the last half of the cells (rows 16 to 31 out of 32 cells for example) and therefore makes you start in the middle. As soon as I would scroll, the first half of the cells (rows 0 to 15) would load and I would be back on row 0 instead of row 15. After that the table would behave fine.
Although a minor problem, the work-around is to simulate a scroll up once the very last cell has been loaded. This triggers the loading of the first cells and brings your table to the "proper" row 0.
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// return whatever the cell height should be
return myCellsArray[indexPath.row]["height"] as CGFloat
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// return whatever the cell height should be
return myCellsArray[indexPath.row]["height"] as CGFloat
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
// Check that the final cell has been loaded and that this is the first time the table is loaded
if indexPath.row == myCellsArray.count - 1 && myStruct.tableHasLoaded == false{
// Reached the bottom
println("bottom reached with cell \(indexPath.row)")
// Simulate a mini scroll backwards as estimatedRowHeight only starts halfway and loads the final half, not the first half of the cells
// This way it forces it to go back to row 0
self.setContentOffset(CGPoint(x: 0, y: -1), animated: false)
// Set bool to true - we don't want to go back to the top everytime we reach the bottom of the table
myStruct.tableHasLoaded = true
}
}