Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何滚动到UITableView的确切结尾?_Ios_Swift_Uitableview - Fatal编程技术网

Ios 如何滚动到UITableView的确切结尾?

Ios 如何滚动到UITableView的确切结尾?,ios,swift,uitableview,Ios,Swift,Uitableview,我有一个UITableView,其中填充了具有动态高度的单元格。当视图控制器从视图控制器中推出时,我希望表格滚动到底部 我尝试了contentOffset和tableView scrollToRowAtIndexPath但是我仍然没有得到我想要的完美解决方案 有人能帮我解决这个问题吗 以下是我要滚动的代码: let indexPath = NSIndexPath(forRow: commentArray.count-1, inSection: 0) tableView.scrollToRowAt

我有一个
UITableView
,其中填充了具有动态高度的单元格。当视图控制器从视图控制器中推出时,我希望表格滚动到底部

我尝试了
contentOffset
tableView scrollToRowAtIndexPath
但是我仍然没有得到我想要的完美解决方案

有人能帮我解决这个问题吗

以下是我要滚动的代码:

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

推送具有tableview的viewcontroller时,只有在tableview完成重新加载后,才应滚动到指定的indexPath

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)

})

将该方法放在dispatch_async中的原因是,一旦执行reloadData,下一行将立即执行,然后在主线程中重新加载。因此,为了知道tableview何时完成(在所有cellforrowindex完成之后),我们在这里使用GCD。基本上,tableview中没有代表会告诉您tableview已完成重新加载。

对于Swift 3.0

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)

})
编写一个函数:

func scrollToBottom(){
    DispatchQueue.main.async {
        let indexPath = IndexPath(row: self.dataArray.count-1, section: 0)
        self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
}
并在重新加载tableview数据后调用它

tableView.reloadData()
scrollToBottom()

这适用于Swift 3.0

let pointsFromTop = CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude)
tableView.setContentOffset(pointsFromTop, animated: true)
在Swift 3+中工作:

        self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.contentSize.height - UIScreen.main.bounds.height), animated: true)

如果使用了具有一定高度的
UINavigationBar
,并且在
UIView
中使用了
UITableView
,请尝试从
UITableView
的帧高度中减去
UINavigationBar
高度。使
UITableView
顶部的
点与
UINavigationBar
底部的
点相同,从而影响
UITableView
底部的
项目滚动

斯威夫特3

[Swift 3,iOS 10]

我最终使用了一种骇人的解决方案,但它不依赖于行索引路径(有时会导致崩溃)、单元格动态高度或表格重新加载事件,因此它看起来非常通用,在实践中比我发现的其他方法更可靠

  • 使用KVO跟踪表的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)
            }
        }
    
  • KVO观察器内部的fire scroll事件

  • 使用延迟计时器计划滚动调用以过滤多个
    观察者触发器

某些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()
    }
}

我将使用更通用的方法:

迅捷4 扩展UITableView{ func scrolltobttom(){ DispatchQueue.main.async{ 设indepath=indepath( 行:self.numberOfRows(第1节:self.numberOfSections-1)-1, 章节:self.numberOfSections-1) 如果HasRowatineXpath(indexPath){ scrollToRow(at:indepath,at:.底部,动画:true) } } } func scroll totop(){ DispatchQueue.main.async{ 设indexPath=indexPath(行:0,节:0) 如果HasRowatineXpath(indexPath){ scrollToRow(at:indepath,at:.top,动画:false) } } } func hasRowAtIndexPath(indexPath:indexPath)->Bool{ 返回indexPath.section
我尝试了Umair的方法,但是在
UITableView
s中,有时可能有一个0行的部分;在这种情况下,代码指向无效的索引路径(空节的第0行不是一行)

盲目地从行/节数中减去1可能是另一个难点,因为行/节可能包含0个元素

下面是我滚动到最底部单元格的解决方案,确保索引路径有效:

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)
    }
}
扩展UITableView{ func scrollToBottomRow(){ DispatchQueue.main.async{ 保护self.numberOfSections>0其他{return} //尝试使用至少有一行的最底部部分 var section=max(self.numberOfSections-1,0) var行=最大值(self.numberOfRows(第节)-1,0) var indepath=indepath(行:行,节:节) //确保索引路径有效,否则请使用上面的部分(可以使用部分 //包含导致无效索引路径的0行) while!self.indexPathIsValid(indexPath){ 截面=最大值(截面-1,0) 行=最大值(self.numberOfRows(第节)-1,0) indexPath=indexPath(行:行,节:节) //如果我们到了最后一节,尝试使用第一行 如果indexath.section==0{ indexPath=indexPath(行:0,节:0) 打破 } } //如果[0,0]是有效的(可能没有数据源?),请确保不会遇到错误 //这里例外 保护self.indexPathIsValid(indexPath)else{return} scrollToRow(at:indepath,at:.底部,动画:true) } } func indexPathIsValid(indexPath:indexPath)->Bool{ 让section=indexPath.section 让row=indexPath.row 返回节
要获得完美的滚动到底部解决方案,请使用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)
        }
    }
在主队列中执行“滚动到底”可以正常工作,因为加载viewController并通过主队列进行延迟后,tableView现在知道了其内容大小,所以它会延迟执行并导致正常工作。
我宁愿在将数据填充到tableView之后使用
self.view.layoutifneed()
,然后调用我的方法
scrollToBottom()
。这对我来说很好。

如果您的tableView为空,请对@Umair answer进行一点更新

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)
    }
}
在Swift 4+中工作:

   self.tableView.reloadData()
    let indexPath = NSIndexPath(row: self.yourDataArray.count-1, section: 0)
    self.tableView.scrollToRow(at: indexPath as IndexPath, at: .bottom, animated: true)

这是一个包含完成闭合器的函数:

func reloadAndScrollToTop(completion: @escaping () -> Void) {
    self.tableView.reload()
    completion()
}
和使用:

self.reloadAndScrollToTop(completion: {
     tableView.scrollToRow(at: indexPath, at: .top, animated: true))
})
安全加载所有表格单元格后,将执行
tableView.scrollTo…
行。

您可以
tableView.scrollRectToVisible(CGRect(x: 0, y: tableView.contentSize.height, width: 1, height: 1), animated: true)
self.view.layoutIfNeeded()
extension UITableView {
    func scrollToBottom(animated:Bool)  {
        let numberOfRows = self.numberOfRows(inSection: self.numberOfSections - 1) - 1
        if numberOfRows >= 0{
            let indexPath = IndexPath(
                row: numberOfRows,
                section: self.numberOfSections - 1)
            self.scrollToRow(at: indexPath, at: .bottom, animated: animated)
        } else {
            let point = CGPoint(x: 0, y: self.contentSize.height + self.contentInset.bottom - self.frame.height)
            if point.y >= 0{
                self.setContentOffset(point, animated: animated)
            }
        }
    }
}
DispatchQueue.main.async {
    let index = IndexPath(row: self.itens.count-1, section: 0)
    self.tableView.scrollToRow(at: index, at: .bottom, animated: true)                        
}           
        
func scrollToBottom(animated: Bool) {
    DispatchQueue.main.async {
        let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height + self.safeAreaBottom)
        
        if bottomOffset.y > 0 {
            self.setContentOffset(bottomOffset, animated: animated)
        }
    }
}
import UIKit

extension UITableView {
    
    func scrollToBottom(animated: Bool) {
        
        DispatchQueue.main.async {
            let point = CGPoint(x: 0, y: self.contentSize.height + self.contentInset.bottom - self.frame.height)
            if point.y >= 0 {
                self.setContentOffset(point, animated: animated)
            }
        }
    }
}
extension UITableView {
   func scrollToBottom(){

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

func scrollToTop() {
    DispatchQueue.main.async { 
        let indexPath = IndexPath(row: 0, section: 0)
        if self.hasRowAtIndexPath(indexPath: indexPath) {
            self.scrollToRow(at: indexPath, at: .top, animated: false)
       }
    }
}

func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
    return indexPath.section < self.numberOfSections && indexPath.row < self.numberOfRows(inSection: indexPath.section)
}
}