web-dev-qa-db-fra.com

Comment faire défiler jusqu'à la fin exacte de UITableView?

J'ai une UITableView qui est peuplée de cellules de hauteur dynamique. Je voudrais que la table défile vers le bas lorsque le contrôleur de vue est poussé à partir du contrôleur de vue.

J'ai essayé avec contentOffset et tableView scrollToRowAtIndexPath mais je ne reçois toujours pas la solution parfaite pour ce que je veux exactement.

Quelqu'un peut-il m'aider s'il vous plaît résoudre ce problème?

Voici mon code pour faire défiler:

let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
31
Padmaja

Pour Swift 3.0

Ecrire une fonction:

func scrollToBottom(){
    DispatchQueue.main.async {
        let indexPath = IndexPath(row: self.dataArray.count-1, section: 0)
        self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
}

et appelez-le après avoir rechargé les données de la table

tableView.reloadData()
scrollToBottom()
65
Amit Verma

J'utiliserais une approche plus générique à ceci:

Swift4

extension UITableView {

    func scrollToBottom(){

        DispatchQueue.main.async {
            let indexPath = IndexPath(
                row: self.numberOfRows(inSection:  self.numberOfSections -1) - 1, 
                section: self.numberOfSections - 1)
            self.scrollToRow(at: indexPath, at: .bottom, animated: true)
        }
    }

    func scrollToTop() {

        DispatchQueue.main.async {
            let indexPath = IndexPath(row: 0, section: 0)
            self.scrollToRow(at: indexPath, at: .top, animated: false)
        }
    }
}
17
Umair Afzal

J'ai essayé l'approche d'Umair, mais dans UITableViews, il peut parfois y avoir une section avec 0 rangée; dans ce cas, le code pointe vers un chemin d'index non valide (la ligne 0 d'une section vide n'est pas une ligne). 

Réduire à l'aveugle 1 du nombre de lignes/sections peut être un autre problème, car à nouveau, la ligne/section peut contenir 0 élément.

Voici ma solution pour faire défiler la cellule la plus en bas, en veillant à la validité du chemin d'index:

extension UITableView {
    func scrollToBottomRow() {
        DispatchQueue.main.async {
            guard self.numberOfSections > 0 else { return }

            // Make an attempt to use the bottom-most section with at least one row
            var section = max(self.numberOfSections - 1, 0)
            var row = max(self.numberOfRows(inSection: section) - 1, 0)
            var indexPath = IndexPath(row: row, section: section)

            // Ensure the index path is valid, otherwise use the section above (sections can
            // contain 0 rows which leads to an invalid index path)
            while !self.indexPathIsValid(indexPath) {
                section = max(section - 1, 0)
                row = max(self.numberOfRows(inSection: section) - 1, 0)
                indexPath = IndexPath(row: row, section: section)

                // If we're down to the last section, attempt to use the first row
                if indexPath.section == 0 {
                    indexPath = IndexPath(row: 0, section: 0)
                    break
                }
            }

            // In the case that [0, 0] is valid (perhaps no data source?), ensure we don't encounter an
            // exception here
            guard self.indexPathIsValid(indexPath) else { return }

            self.scrollToRow(at: indexPath, at: .bottom, animated: true)
        }
    }

    func indexPathIsValid(_ indexPath: IndexPath) -> Bool {
        let section = indexPath.section
        let row = indexPath.row
        return section < self.numberOfSections && row < self.numberOfRows(inSection: section)
    }
}
10
John Rogers

Lorsque vous appuyez sur le contrôleur de vue ayant la vue de table, vous devez faire défiler le chemin indexPath spécifié uniquement après le rechargement de votre vue Table.

yourTableview.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
    let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0)
  tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)

})

La raison de placer la méthode dans dispatch_async est une fois que vous exécutez reloadData, la ligne suivante sera exécutée immédiatement, puis le rechargement aura lieu dans le thread principal. Pour savoir quand la tableview est terminée (une fois que cellforrowindex est terminé), nous utilisons GCD ici. En principe, aucun représentant dans tableview n'indiquera que le rechargement de la vue de la table est terminé.

6
iPrabu

Cela fonctionne dans Swift 3.0

let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude)
tableView.setContentOffset(pointsFromTop, animated: true)
2
silly_cone

Pour une solution parfaite du défilement vers le bas, utilisez tableView contentOffset

func scrollToBottom()  {
        let point = CGPoint(x: 0, y: self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.frame.height)
        if point.y >= 0{
            self.tableView.setContentOffset(point, animated: animate)
        }
    }

Je préfère utiliser self.view.layoutIfNeeded() après avoir renseigné mes données dans tableView, puis appeler ma méthode scrollToBottom(). Cela fonctionne bien pour moi.

1
Jayraj Vala

[Swift 3, iOS 10]

J'ai finalement eu recours à une solution simple, mais elle ne dépend pas des indexpaths des lignes (ce qui entraîne parfois des plantages), de la hauteur dynamique des cellules ou de l'événement de rechargement de table. d'autres que j'ai trouvés. 

  • utiliser KVO pour suivre le contenu de la table

  • événement de parchemin de feu à l'intérieur d'un observateur du KVO

  • planifiez l'appel du défilement à l'aide d'un minuteur différé
    observateurs déclencheurs

Le code à l'intérieur de ViewController:

private var scrollTimer: Timer?
private var ObserveContext: Int = 0

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    table.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: &ObserveContext)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    table.removeObserver(self, forKeyPath: "contentSize")
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (context == &ObserveContext) {
        self.scheduleScrollToBottom()
    }
}

func scheduleScrollToBottom() {

    if (self.scrollTimer == nil) {
        self.scrollTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] (timer) in
            let table = self!.table

            let bottomOffset = table.contentSize.height - table.bounds.size.height
            if !table.isDragging && bottomOffset > 0 {
                let point: CGPoint = CGPoint(x: 0, y: bottomOffset)
                table.setContentOffset(point, animated: true)
            }

            timer.invalidate()
            self?.scrollTimer = nil
        })
        self.scrollTimer?.fire()
    }
}
0
Mitya

Une petite mise à jour de @Umair answer au cas où votre tableView serait vide

func scrollToBottom(animated: Bool = true, delay: Double = 0.0) {
    let numberOfRows = tableView.numberOfRows(inSection: tableView.numberOfSections - 1) - 1
    guard numberOfRows > 0 else { return }

    DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [unowned self] in

        let indexPath = IndexPath(
            row: numberOfRows,
            section: self.tableView.numberOfSections - 1)
        self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: animated)
    }
}
0
mattyU

Si vous avez utilisé UINavigationBar avec une certaine hauteur et UITableView dans UIView, essayez de soustraire UINavigationBar height de la hauteur du cadre UITableView. Faites en sorte que votre UITableView 's top soit identique à UINavigationBar' s bottom afin que cela affecte le défilement de vos UITableView's bottom.

Swift 3

simpleTableView.frame = CGRect.init(x: 0, y: navigationBarHeight, width: Int(view.frame.width), height: Int(view.frame.height)-navigationBarHeight)
0
elia