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)
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()
J'utiliserais une approche plus générique à ceci:
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)
}
}
}
J'ai essayé l'approche d'Umair, mais dans UITableView
s, 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)
}
}
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é.
Cela fonctionne dans Swift 3.0
let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude)
tableView.setContentOffset(pointsFromTop, animated: true)
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.
[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()
}
}
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)
}
}
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)