I have a UITableView
that is populated with cells with dynamic height. I would like the table to scroll to the bottom when the view controller is pushed from vi
[Swift 3, iOS 10]
I've ended up using kind-of-hacky solution, but it doesn't depend on rows indexpaths (which leads to crashes sometimes), cells dynamic height or table reload event, so it seems pretty universal and in practice works more reliable than others I've found.
use KVO to track table's contentOffset
fire scroll event inside KVO observer
schedule scroll invocation using delayed Timer to filter multiple
observer triggers
The code inside some 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()
}
}