22

I have a list of numbers in a table view like this.

enter image description here

As you can see the numbers are repeated. Let's consider a set of repeated numbers as a group. So there are group of 1s, group of 2s and so on.

What I want to do is when the app starts, I need to scroll to the start position of a specified group automatically. Before explaining further, here is my code so far.

import UIKit

class TableViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate {

    private var scrollToTime = true
    private var items = [Int]()
    private var groupNoToScroll = 12

    override func viewDidLoad() {
        super.viewDidLoad()

        items = [1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 4, 5, 5, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 16, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20, 21, 22, 22, 23, 23, 23]
    }

    // MARK: - UITableViewDataSource
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
        cell.textLabel?.text = String(items[indexPath.row])

        return cell
    }

    // MARK: - UITableViewDelegate
    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

        let lastRow = tableView.indexPathsForVisibleRows()?.last as NSIndexPath
        if indexPath.row == lastRow.row {
            if scrollToTime == true {
                let indexPath = NSIndexPath(forRow: groupNoToScroll, inSection: 0)
                tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
                scrollToTime = false
            }
        }
    }
}

I've assigned the value 12 for the variable groupNoToScroll. This means when the app starts I want the table view to be auto scrolled to the start of the cells of the 12s group.

But currently what my code does is scroll to the 12th cell, not the cell that has the value 12. My question is how can I check with the cells' values and scroll to the number which I specify?

Isuru
  • 30,617
  • 60
  • 187
  • 303
  • 1
    You will have to iterate through your array and get the index of the first appearance of `groupNoToScroll`. – Hermann Klecker Jan 20 '15 at 11:06
  • 1
    However, it is generally not a good idea overriding tableView:willDisplayCell: . What are you really up to? – Hermann Klecker Jan 20 '15 at 11:07
  • @HermannKlecker I wanted to do the automatic scrolling after all the cells have been loaded. So according to [this](http://stackoverflow.com/a/11672379/1077789) answer, `willDIsplayCell` is where I need to check for that. Is there a better way to go about this? – Isuru Jan 20 '15 at 11:21
  • No willDisplayCell is not designed for that. You will risk an infinite loop. I would just scroll to the related cell in viewDidLoad by using scrollToRowAtIndexPath. Works fine. I did that a lot. – Hermann Klecker Jan 20 '15 at 11:47

4 Answers4

52

You can use find the index of the item(which will be its row), and then scroll to that index.
find function returns the index of a particular element in the array.

if let index = find(items, groupNoToScroll)
{
    let indexPath = NSIndexPath(forRow: index, inSection: 0)
    tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
lenooh
  • 10,364
  • 5
  • 58
  • 49
rakeshbs
  • 24,392
  • 7
  • 73
  • 63
  • Wow that's a neat function. Thank you so much. That's surprisingly simple and cleaner syntax. Very cool. – Isuru Jan 20 '15 at 11:24
  • I don't agree with using the `willDisplayCell` function though. I would do it after calling `reloadData`. http://stackoverflow.com/questions/16071503/how-to-tell-when-uitableview-has-completed-reloaddata – rakeshbs Jan 20 '15 at 11:34
  • if lastRow.row is the very row that corresponds to the actual index, which may be the case when the size of each group is one, then you run into an infinite loop here. scrollToRowAtIndexPath can cause willDisplayCell to be invoked. – Hermann Klecker Jan 20 '15 at 11:50
  • 1
    @HermannKlecker Okay, I'm doing the auto scrolling in the `viewDidAppear` method because I want the scrolling to happen every time when I leave this view and come back. – Isuru Jan 20 '15 at 19:51
  • @rakeshbs Is it necessary to call `reloadData()` every time before doing the auto scroll? If so can you please tell me the reason? – Isuru Jan 20 '15 at 19:52
  • No I didn't say so. I just said do it after loading the data into the `UITableView`. Since you are doing it in `viewDidAppear` make sure that the `UITableView` has finished loading the data before calling the scroll function. – rakeshbs Jan 21 '15 at 01:24
  • Hello again, I'm facing a small situation related to this solution. If the `groupNoToScroll` I specify doesn't exist in the `items` array. So it doesn't scroll. Is there a way to make it like if the specified value doesn't exist, scroll to the next closest value? – Isuru Feb 08 '15 at 14:00
  • you have to define a function to find the next closest value, and then scroll to it. there is no built in way to do that – rakeshbs Feb 09 '15 at 08:03
  • The find function no longer is available. Here describes alternatives: https://stackoverflow.com/questions/24028860/how-to-find-index-of-list-item-in-swift – Mikael Weiss Aug 03 '20 at 23:07
20

Swift 4:

let indexPath = IndexPath(row: row, section: section)
tableView.scrollToRow(at: indexPath, at: .top, animated: true)

(first, of course, you have to assign values to row and section based on which cell you want to scroll to)

Gefilte Fish
  • 1,600
  • 1
  • 18
  • 17
12

Swift 3.0

 let indexPath = NSIndexPath(forRow: 5, inSection: 0)
 tableView.scrollToRow(at: indexPath, at: .top, animated: true)

Swift 4.0

let lastRowIndex = self.tblRequestStatus!.numberOfRows(inSection: 0) - 1
let pathToLastRow = IndexPath.init(row: lastRowIndex, section: 0)
tableView.scrollToRow(at: pathToLastRow, at: .none, animated: false)
Harshil Kotecha
  • 2,846
  • 4
  • 27
  • 41
3

Do this to find the first element in the array that is equal to groupNoToScroll. Then, go to that row.

    var rowToGoTo:Int = 0 //Rather use the Swift find function.
    for x in items{
        if x == groupNoToScroll{
            break
        }
        rowToGoTo++
    }

    let lastRow = tableView.indexPathsForVisibleRows()?.last as NSIndexPath
    if indexPath.row == lastRow.row {
        if scrollToTime == true {
           let indexPath = NSIndexPath(row: rowToGoTo, section: 0)
           tblBusList.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
        }
    }

But I would recommend doing this in viewDidAppear.

As Isuru pointed out rather use the find function.

Krunal Nagvadia
  • 1,083
  • 2
  • 12
  • 33
Petri
  • 1,020
  • 3
  • 14
  • 24
  • Thank you for the answer. While the logic of your answer is identical to @rakeshbs's answer, I accepted his because of usage of the Swift function `find` which I thought a more elegant and a cleaner way of doing it. But thanks again :) – Isuru Jan 20 '15 at 11:23
  • That makes perfect sense, I would too. I forgot about the find function. – Petri Jan 20 '15 at 11:30