0

Im implementing a UITableView and I would like for the table view to automatically scroll up to the top once the user begins scrolling. I have used logic similar to this post to detect the direction that the user is scrolling. When the user begins to scroll up the function below detects the scroll but table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) goes very slowly and stops part way up to the top of the cell. How can I fix this.

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    print("Dragging has ended")
    //print("lastContentOffSet: \(self.lastContentOffset)")
    if (self.lastContentOffset < scrollView.contentOffset.y) {
        // moved to top
        print("Scrolled down")
    } else if (self.lastContentOffset > scrollView.contentOffset.y) {
        // moved to bottom
        print("Scrolled up")
        table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
    } else {
        // didn't move
    }
}

Below is the code in its entirety.

import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

    @IBOutlet weak var table:UITableView!
    @IBOutlet weak var photosButton: UIButton!

    var lastContentOffset: CGFloat = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self 
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return  2
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell:UITableViewCell = UITableViewCell()
        if indexPath.row == 1{
            cell = table.dequeueReusableCell(withIdentifier: "cameraCell", for: indexPath) as! cameraCell
            cell.backgroundColor = .red
        }else{
            cell = table.dequeueReusableCell(withIdentifier: "photoAlbumCell", for: indexPath) as! photoAlbumCell
            cell.backgroundColor = .blue
        }
        cell.selectionStyle = UITableViewCellSelectionStyle.none
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UIScreen.main.bounds.height
    }
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        print("Scrolling began")
        self.lastContentOffset = scrollView.contentOffset.y
    }
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        print("Dragging has ended")
        if (self.lastContentOffset < scrollView.contentOffset.y) {
            // moved to top
            print("Scrolled down")
        } else if (self.lastContentOffset > scrollView.contentOffset.y) {
            // moved to bottom
            print("Scrolled up")
            table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
        } else {
            // didn't move
        }
    }
    @IBAction func photoAlbumButton(_ sender: UIButton) {
        table.scrollToRow(at: IndexPath(row: 1, section: 0), at: .top, animated: true)
    }
}

class cameraCell:UITableViewCell{
    override func awakeFromNib() {
        super.awakeFromNib()
    } 
 }
class photoAlbumCell: UITableViewCell {
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}
Gaurav Bharti
  • 789
  • 1
  • 11
  • 22

1 Answers1

1

There are 2 solutions for the above issue -

Solution 1 - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) has to be handled when the user ends dragging. To handle for normal scroll and leave when decelerating, use func scrollViewDidEndDecelerating(_ scrollView: UIScrollView). Please note you'll need both of these delegate methods to handle your scrolling. Both of the delegates will be executing the same set of code. The only change is in the way the first delegate executes the code, i.e., you need to add !decelerate condition since we're handling in the second delegate for the decelerate scenario. Please note that after lifting the finger, it scrolls slowly till the delegate is called, but it makes sure that it scrolls to the top. In other words, your code should look something like below -

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    print("Dragging has ended")
    if (self.lastContentOffset < scrollView.contentOffset.y) {
        // moved to top
        print("Scrolled down")
    } else if (self.lastContentOffset > scrollView.contentOffset.y) {
        // moved to bottom
        print("Scrolled up")
        table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
    } else {
        // didn't move
    }
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate { // decelerate logic will be handled above
        print("Dragging has ended")
        if (self.lastContentOffset < scrollView.contentOffset.y) {
            // moved to top
            print("Scrolled down")
        } else if (self.lastContentOffset > scrollView.contentOffset.y) {
            // moved to bottom
            print("Scrolled up")
            table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
        } else {
            // didn't move
        }
    }
}

Solution 2 - The easiest fix would be to encapsulate the table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) inside Dispatch.main forcing it to run in the main thread (although it's running in the main thread, for scrolling purpose you'll need it to run it forcefully). However, I haven't tested this solution in a real device, so not sure if this shows some jerk effect.

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    print("Dragging has ended")
    if (self.lastContentOffset < scrollView.contentOffset.y) {
        // moved to top
        print("Scrolled down")
    } else if (self.lastContentOffset > scrollView.contentOffset.y) {
        // moved to bottom
        print("Scrolled up")
        DispatchQueue.main.async {
            self.table.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
        }

    } else {
        // didn't move
    }
}

I personally would prefer the first solution since that is the way that you can properly handle for both dragging and decelerating scenarios.

Varun
  • 763
  • 6
  • 12
  • I added `scrollViewWillBeginDecelerating` as well with same code inside along with your solution and it worked very well thank you so much for all of your help. – TheRedCamaro3.0 3.0 Mar 07 '18 at 22:53