【问题标题】:Automatically scrolling to the bottom of a table with UITableViewAutomaticDimension row height? - Swift, iOS 8+使用 UITableViewAutomaticDimension 行高自动滚动到表格底部? - 斯威夫特,iOS 8+
【发布时间】:2015-10-24 12:10:31
【问题描述】:

通常,在表格中,可以使用以下代码自动滚动到底部:

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

但是,当我将其设置为在 viewDidLoad() 中使用自动行高时,如下所示,上面的代码仅向下滚动大约一半(取决于行高和行数 - 它只发生在多行中远大于估计的RowHeight)。

tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension

如何在使用 UITableViewAutomaticDimension 时以编程方式向下滚动到表格底部?

【问题讨论】:

  • 我知道它是旧的,但你尝试过 contentSize 属性吗?

标签: ios swift cocoa-touch


【解决方案1】:

在使用自动调整单元格大小的表格视图底部插入行时,我遇到了非常相似的问题,这是我能找到的唯一解决方法。它并不漂亮,但可以完成工作(在 iOS 8 和 9 上测试)。 在插入时:

// compute the insertion index as the last one
let insertionIndex = NSIndexPath(forRow: elements.count - 1, inSection: 0)
// delete from the top, insert at the bottom
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths(deletionsIndexes, withRowAnimation: .Top)
tableView.insertRowsAtIndexPaths([insertionIndex], withRowAnimation: .Fade)
tableView.endUpdates()
// scroll to the last row, position will be wrong
tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Bottom, animated: false)
// scroll back to the one before, the actual scrolling will be done after inserting the cell
if elements.count > 1 {
     let prevIndex = NSIndexPath(forRow: lastIndex.row - 1, inSection: 0)
     self.tableView.scrollToRowAtIndexPath(prevIndex, atScrollPosition: .Bottom, animated: false)
}

如您所见,我们滚动到最后一行只是为了触发行插入 然后滚动回之前的行(全部没有动画)以避免任何可见的滚动。 然后在插入单元格时:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    [...]

    let cell = try configuredCell(forElement: element)
    dispatch_async(dispatch_get_main_queue()) {
        let lastIndex = NSIndexPath(forRow: self.elements.count - 1, inSection: 0)
        // when inserting the last element we do the actual animated scrolling
        if indexPath.row == self.elements.count - 1 {
            tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Bottom, animated: true)
        }
    }
    return cell
}

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,添加这样的延迟帮助我解决了这个问题:

    let delay = 0.1 * Double(NSEC_PER_SEC)
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
    
    dispatch_after(time, dispatch_get_main_queue(), {
        let indexPath = NSIndexPath(forRow: rowCount - 1, inSection: 0)
        self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
    })
    

    【讨论】:

    • 我认为这是单元格尚未呈现的问题?我正在使用 50 行,即使有 10 秒的延迟,它仍然没有完全滚动到底部。
    • 是的,似乎是渲染时间问题。它对你有用吗?
    • 查看我编辑的评论 - 我在输入完之前不小心按了 Enter
    • 我明白了。您能否尝试实现tableView:estimatedHeightForRowAtIndexPath: 并为您确定的单元格返回尽可能接近的高度。您可以在覆盖其余单元格的else 部分中返回UITableViewAutomaticDimension 。这可能是错误的单元格高度计算 - 只是一个想法。
    • 我有一个表格视图,每个单元格都有一个标签,大约 50 高,大约 200+ 高。如果我使用estimatedHeightForRowAtIndexPath 并计算该函数中的行数,我可以获得更准确的数字,它确实滚动到底部,但代码不是很整洁 - 这是我唯一的选择(计算行数,乘以字体大小并添加填充)?
    【解决方案3】:

    实际上,UITableView 的这种“错误行为”的原因是行高估计。当使用动态单元格高度时,tableView.contentSize 和例如 [tableView rectForHeaderInSection: lastSection] 的结果对于尚未显示的项目(例如仅具有估计布局矩形的项目。例如,矩形部分可能位于 contentSize 之外或内部某处,即使内部没有单元格也是如此)。

    此外,scollRectToVisible: 似乎只适用于有效的矩形。因此,以当前对表格视图项的错误估计来调用它可能只会导致任何结果......

    我对这个问题的解决方案是一个 UITableView 类别,它只是迭代几次以滚动到所需的位置。所以 UITableView 有机会计算真正的项目布局矩形(页眉、单元格、页脚)。我的测试表明,在大多数情况下,只需要一个循环。

    UITableView+LEAScrollToVisible.h

    //
    //  UITableView+LEAScrollToVisible.h
    //
    //  Copyright © 2016 LaborEtArs. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    
    
    /**
     UITableView (LEAScrollToVisible)
    
     */
    @interface UITableView (LEAScrollToVisible)
    
    /*
     scrollSectionHeaderToVisible:withCompletion_LEA:
    
     */
    - (void)scrollSectionHeaderToVisible:(NSInteger)pSectionHeaderIndex
                      withCompletion_LEA:(void (^)(void))pCompletion;
    
    /*
     scrollRowToVisible:withCompletion_LEA:
    
     */
    - (void)scrollRowToVisible:(NSIndexPath *)pRowIndexPath
            withCompletion_LEA:(void (^)(void))pCompletion;
    
    /*
     scrollSectionFooterToVisible:withCompletion_LEA:
    
     */
    - (void)scrollSectionFooterToVisible:(NSInteger)pSectionFooterIndex
                      withCompletion_LEA:(void (^)(void))pCompletion;
    
    @end
    

    UITableView+LEAScrollToVisible.m

    //
    //  UITableView+LEAScrollToVisible.m
    //
    //  Copyright © 2016 LaborEtArs. All rights reserved.
    //
    
    #import "UITableView+LEAScrollToVisible.h"
    
    
    /**
     UITableView (LEAScrollToVisible)
    
     */
    @implementation UITableView (LEAScrollToVisible)
    
    /*
     scrollSectionHeaderToVisible:withCompletion_LEA:
    
     */
    - (void)scrollSectionHeaderToVisible:(NSInteger)pSectionHeaderIndex
                      withCompletion_LEA:(void (^)(void))pCompletion {
        //FLog;
        NSAssert((pSectionHeaderIndex < self.numberOfSections), @"Invalid section header index: %li", (long int)pSectionHeaderIndex);
    
        // visible part of the scroll view
        CGRect  visibleRect = CGRectMake((self.contentInset.left + self.contentOffset.x),
                                         (self.contentInset.top + self.contentOffset.y),
                                         (CGRectGetWidth(self.frame) - (self.contentInset.left + self.contentInset.right)),
                                         (CGRectGetHeight(self.frame) - (self.contentInset.top + self.contentInset.bottom)));
    
        // maximum content offset (to avoid to scroll too far)
        CGFloat maxPossibleContentOffset = MAX((-self.contentInset.top),
                                               (self.contentSize.height -
                                                CGRectGetHeight(visibleRect) -
                                                self.contentInset.top));
    
        // the rect for the header view (maybe just estimated)
        CGRect  sectionHeaderRect = [self rectForHeaderInSection:pSectionHeaderIndex];
    
        // the theoretical offset to show the header rect topmost in the table view visible area
        CGFloat contentOffsetToBeVisibleAtTop = (CGRectGetMinY(sectionHeaderRect) - self.contentInset.top);
    
        // the real target offset
        CGFloat targetContentOffsetY = MIN(contentOffsetToBeVisibleAtTop, maxPossibleContentOffset);
    
        if (0.5 <= fabs(self.contentOffset.y - targetContentOffsetY)) {
            // current offset is different -> move
    
            // disable user interaction to avoid disturbances
            self.userInteractionEnabled = NO;
    
            // Scroll to target offset
            [self setContentOffset:CGPointMake(self.contentOffset.x, targetContentOffsetY)
                          animated:YES];
    
            // Reiterate after waiting for the animation to finalize
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
                           dispatch_get_main_queue(),
                           ^{
                               // Reenable user interaction
                               self.userInteractionEnabled = YES;
    
                               [self scrollSectionHeaderToVisible:pSectionHeaderIndex
                                               withCompletion_LEA:pCompletion];
                           });
    
        } else {
            // current offset fits -> execute completion block
            if (pCompletion) {
                pCompletion();
            }
        }
    }
    
    /*
     scrollRowToVisible:withCompletion_LEA:
    
     */
    - (void)scrollRowToVisible:(NSIndexPath *)pRowIndexPath
            withCompletion_LEA:(void (^)(void))pCompletion {
        //FLog;
        NSAssert((pRowIndexPath.section < self.numberOfSections), @"Invalid index path: %@", pRowIndexPath);
        NSAssert((pRowIndexPath.row < [self numberOfRowsInSection:pRowIndexPath.section]), @"Invalid index path: %@", pRowIndexPath);
    
        // visible part of the scroll view
        CGRect  visibleRect = CGRectMake((self.contentInset.left + self.contentOffset.x),
                                         (self.contentInset.top + self.contentOffset.y),
                                         (CGRectGetWidth(self.frame) - (self.contentInset.left + self.contentInset.right)),
                                         (CGRectGetHeight(self.frame) - (self.contentInset.top + self.contentInset.bottom)));
    
        // maximum content offset (to avoid to scroll too far)
        CGFloat maxPossibleContentOffset = MAX((-self.contentInset.top),
                                               (self.contentSize.height -
                                                CGRectGetHeight(visibleRect) -
                                                self.contentInset.top));
    
        // the rect for the cell view (maybe just estimated)
        CGRect  cellRect = [self rectForRowAtIndexPath:pRowIndexPath];
    
        // the theoretical offset to show the row rect topmost in the table view visible area
        CGFloat contentOffsetToBeVisibleAtTop = (CGRectGetMinY(cellRect) - self.contentInset.top);
    
        // the real target offset
        CGFloat targetContentOffsetY = MIN(contentOffsetToBeVisibleAtTop, maxPossibleContentOffset);
    
        if (0.5 <= fabs(self.contentOffset.y - targetContentOffsetY)) {
            // current offset is different -> move
    
            // disable user interaction to avoid disturbances
            self.userInteractionEnabled = NO;
    
            // Scroll to target offset
            [self setContentOffset:CGPointMake(self.contentOffset.x, targetContentOffsetY)
                          animated:YES];
    
            // Reiterate after waiting for the animation to finalize
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
                           dispatch_get_main_queue(),
                           ^{
                               // Reenable user interaction
                               self.userInteractionEnabled = YES;
    
                               [self scrollRowToVisible:pRowIndexPath
                                     withCompletion_LEA:pCompletion];
                           });
    
        } else {
            // current offset fits -> execute completion block
            if (pCompletion) {
                pCompletion();
            }
        }
    }
    
    /*
     scrollSectionFooterToVisible:withCompletion_LEA:
    
     */
    - (void)scrollSectionFooterToVisible:(NSInteger)pSectionFooterIndex
                      withCompletion_LEA:(void (^)(void))pCompletion {
        //FLog;
        NSAssert((pSectionFooterIndex < self.numberOfSections), @"Invalid section footer index: %li", (long int)pSectionFooterIndex);
    
        // visible part of the scroll view
        CGRect  visibleRect = CGRectMake((self.contentInset.left + self.contentOffset.x),
                                         (self.contentInset.top + self.contentOffset.y),
                                         (CGRectGetWidth(self.frame) - (self.contentInset.left + self.contentInset.right)),
                                         (CGRectGetHeight(self.frame) - (self.contentInset.top + self.contentInset.bottom)));
    
        // maximum content offset (to avoid to scroll too far)
        CGFloat maxPossibleContentOffset = MAX((-self.contentInset.top),
                                               (self.contentSize.height -
                                                CGRectGetHeight(visibleRect) -
                                                self.contentInset.top));
    
        // the rect for the footer view (maybe just estimated)
        CGRect  sectionFooterRect = [self rectForFooterInSection:pSectionFooterIndex];
    
        // the theoretical offset to show the footer rect topmost in the table view visible area
        CGFloat contentOffsetToBeVisibleAtTop = (CGRectGetMinY(sectionFooterRect) - self.contentInset.top);
    
        // the real target offset
        CGFloat targetContentOffsetY = MIN(contentOffsetToBeVisibleAtTop, maxPossibleContentOffset);
    
        if (0.5 <= fabs(self.contentOffset.y - targetContentOffsetY)) {
            // current offset is different -> move
    
            // disable user interaction to avoid disturbances
            self.userInteractionEnabled = NO;
    
            // Scroll to target offset
            [self setContentOffset:CGPointMake(self.contentOffset.x, targetContentOffsetY)
                          animated:YES];
    
            // Reiterate after waiting for the animation to finalize
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
                           dispatch_get_main_queue(),
                           ^{
                               // Reenable user interaction
                               self.userInteractionEnabled = YES;
    
                               [self scrollSectionFooterToVisible:pSectionFooterIndex
                                               withCompletion_LEA:pCompletion];
                           });
    
        } else {
            // current offset fits -> execute completion block
            if (pCompletion) {
                pCompletion();
            }
        }
    }
    
    @end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      • 2016-12-31
      • 1970-01-01
      • 1970-01-01
      • 2017-07-22
      • 2017-03-24
      • 1970-01-01
      相关资源
      最近更新 更多