I am trying to make a table view that can add a cell when it's dragged downwards up to a certain content offset. Something like the animations in the following links:
Animation1
Animation2
I have tried using the delegate function: scrollViewDidScroll(_:) but I'll need to stop the function once the cell is added.
My Code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y <= -110 {
cellCount += 1
dataSource?.append(Model())
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
}
How to achieve the animations in the links?
Check this Github Repo FlippingNotch
quickbirdstudios has shown how to make first animation
Related
I have an expandable UITableView. When sections tapped, they expand or collapse with animation (Scroll). My problem is that there is a weird animation when expanding or collapsing headers. UITableView scrolls to top and then goes to the tapped cell. In addition, when there is no expanded cell, sometimes, It doesn't scroll to top and there is a big space between top header and top view of UITableView.
My problem is that I need to scroll to expanded section and also get rid of scroll to top bug.
I tried below solution but didn't work for me:
prevent table view to scrolling top after insertRows
It also looks like same problem with below question, but can't figure out how to implement it.
Why does my UITableView "jump" when inserting or removing a row?
How I toggle selection:
func toggleSection(header: DistrictTableViewHeader, section: Int) {
print("Trying to expand and close section...")
// Close the section first by deleting the rows
var indexPaths = [IndexPath]()
for row in self.cities[section].districts.indices {
print(0, row)
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
let isExpanded = self.cities[section].isExpanded
if(isExpanded){
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Collapse Click")
}else{
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Expand Click")
}
self.cities[section].isExpanded = !isExpanded
// This call opens CATransaction context
CATransaction.begin()
// This call begins tableView updates (not really needed if you only make one insertion call, or one deletion call, but in this example we do both)
tableView.beginUpdates()
if isExpanded {
tableView.deleteRows(at: indexPaths, with: .automatic)
} else {
tableView.insertRows(at: indexPaths, with: .automatic)
}
// completionBlock will be called after rows insertion/deletion animation is done
CATransaction.setCompletionBlock({
// This call will scroll tableView to the top of the 'section' ('section' should have value of the folded/unfolded section's index)
if !isExpanded{
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: .top, animated: true)
}
})
if self.scrollToTop(){
self.tableView.setContentOffset(.zero, animated: true)
}
// End table view updates
tableView.endUpdates()
// Close CATransaction context
CATransaction.commit()
}
private func scrollToTop() -> Bool{
for sec in self.cities{
if(sec.isExpanded){
return false
}
}
return true
}
I'm giving height of cell inside;
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
How I declare headers;
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = DistrictTableViewHeader()
header.isColapsed = !self.cities[section].isExpanded
header.customInit(title: self.cities[section].name, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
EDIT: Solution in this question (Setting estimated height to 0) looks like working when inserting row. However, I still have bug when deleting rows. Bottom header goes to center of tableview and then goes to bottom after collapse header.
iOS 11 Floating TableView Header
You can try using below code. Just get the last content offset of your tableview. Then do the update and reassign the content offset.
let lastOffset = tableView.contentOffset
self.tableView.beginUpdates()
self.tableView.layer.removeAllAnimations()
self.tableView.endUpdates()
tableView.contentOffset = lastOffset
Instead of tableView.beginUpdates() and tableView.endUpdates(), In my Code i'm using tableView.reloadData() for expanding and contracting the particular section, You can call reloadData when you need to provide expansion of section.This results that you don't have the problem of animation scroll to the top. And works fine in my project where I have to show number of rows in particular section on a click of button which includes in that section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}
// Ignore the If Else statements it's just when do you need expansion of section.
else {
if showMore == true {
return self.userPoints.rewardsData[section - 1].count - 1
}
else {
return 0
}
}
}
Also Don't Forget to increase or decrease the number of rows to that particular section accordingly.Previous line is important to avoid any crash.
Simple Solution swift3 and Above
use below extension as
eg: tableViewOutlet.tableViewScrollToBottom(animated: true)
extension UITableView {
func tableViewScrollToBottom(animated: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
let numberOfSections = self.numberOfSections
let numberOfRows = self.numberOfRows(inSection: numberOfSections-1)
if numberOfRows > 0 {
let indexPath = IndexPath(row: 0, section: 0)
self.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.top, animated: animated)
}
}
}
}
I also facing same issue but after read some tutorials and research & Analysis I got the this issue occurred due to height of cell when you expand the section at that tableview count height of cell from 0 to 120(as per your cell height).
In my case I solved that issue using estimated height of cell.
I hope that will help you,
Thanks
I have a collectionView called timeline that is scrolling programmatically on the second to last line here:
internal func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if !onceOnly {
let indexToScrollTo = IndexPath(row: self.posts.count - 1, section: 0)
collectionView.scrollToItem(at: indexToScrollTo, at: .left, animated: false)
let firstPost = posts.first?.timeStamp
let firstOfFirstMonth = firstPost?.startOfMonth()
let diff = posts.last?.timeStamp.months(from: firstOfFirstMonth!)
//self.currentPostMonth = diff
let monthCellIndexPath = IndexPath(row: diff!, section: 0)
timeline.scrollToItem(at: monthCellIndexPath, at: .centeredHorizontally, animated: false)
onceOnly = true
}
}
later.. I am attempting to detect that this has finished scrolling with
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView == timeline {
print("Did finish")
}
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if scrollView == timeline {
print("Did finish")
}
}
Neither print statement fires once the scroll has completed. I think partially because animation = false. When I set that to true, it prints Did finish correctly - I think specifically scrollViewDidEndScrollingAnimation prints even though scrollViewDidEndDecelerating still does nothing because its being scrolled programatically.
How can I detect that this scroll has finished?
I think because you compare the collectionView with the scrollView
if scrollView == timeline
which is false , BTW you can use
func scrollViewDidScroll(_ scrollView: UIScrollView)
If there is no animation then in the next line after
timeline.scrollToItem(at: monthCellIndexPath, at: .centeredHorizontally, animated: false)
run what you want as it's will occur serially where scrolling ended successfully , no need to set it inside scrollView's delegate methods whether it's called or not
I have a table with number of rows. I would like to pre-scroll table after it's load and it's not so hard, but I have a problem. Table become non-scrollable after scrollToRow execution.
Here is my code scroll function:
func scrollToValue() {
var scrollPosition: Int = 30
let indexPath = IndexPath(row: scrollPosition, section: 0)
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
}
and I execute it in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
scrollToValue()
}
Here is what I have executed this code and try to move table top or bottom: table moves back to 15.0 value at the middle after my interaction, but I would like to scroll to any other position top and bottom without rollback. Even though if I choose false for animation scrollToRow(at: indexPath, at: .middle, animated: false) I can't move cells at all. I can see scroll indicator at the right but cells are frozen.
Remove scroll action from layoutSubviews as tableView is still not loaded and try this
override func viewDidAppear() {
scrollToValue()
}
I'm trying to animate move items between sections after selecting row.
This is code I'm using for moving items from section 0 to section 1:
tableSource[indexPath.section].tableItems.remove(at: indexPath.row)
tableSource[1].tableItems.insert(data, at: 0)
tableView.moveRow(at: indexPath, to: IndexPath(row: 0, section: 1))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 1)], with: UITableViewRowAnimation.automatic)
})
DispatchQueue is used to update row after animation.
It works perfectly if all items fit in the screen or if section 1 is visible. Otherwise, I get the behavior as when you delete a row and add it to another index.
It's because UITableView doesn't know where to animate if cell isn't visible
I need the cells of a UITableView to be expanded, but I see a flicker when doing this.
I have this implementation:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Switch row state
expandedFlags[indexPath.row] = !expandedFlags[indexPath.row]
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: { () -> Void in
tableView.beginUpdates()
tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
}, completion: nil)
}
Is there any way to avoid the flicking when reloading the cells? I've read several posts but didn't work for my scenario.
tableView.beginUpdates()
....
tableView.endUpdates()
already has its own animation UIView animation what probably makes the flicker
Hi After spending so many hours on this, I finally found the solution to stop this flicker issue in table view while you are expanding or collapsing your tableview cell.
In First step, you have to get your current offset of tableview and then stop the uiview animation and tell your tableview that you are going to update some rows in the tableview. Then pass your current index [IndexPath(row: 0, section: 3)] like this is mine. Your tableview works like charm.
let currentOffset = self.tableView.contentOffset
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 3)], with: .none)
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
self.tableView.setContentOffset(currentOffset, animated: false)