在 uitableview 上加载更多向上滚动的项目

Posted

技术标签:

【中文标题】在 uitableview 上加载更多向上滚动的项目【英文标题】:Load more items scrolling up on uitableview 【发布时间】:2014-12-11 18:03:00 【问题描述】:

我正在用 cmets 制作 UITableView。我在底部有最新的 cmets,在顶部有较旧的 cmets。

我将 cmets 保存在 NSMutableArray 中。当我到达顶部时,我想加载更多较旧的 cmets。我该怎么做?

【问题讨论】:

【参考方案1】:

你需要几个方法:

获取方法 - 这取决于您如何实现数据检索。假设默认情况下您只显示最近的 10 个 cmets。如果您的阵列仅包含这 10 个 cmets,那么您需要一种机制来执行本地或远程数据库的提取,以提取另外 10 个额外的 cmets 并将它们添加到阵列中。如果该数组始终包含与该帖子相关的所有 cmets(在本例中为 56 个 cmets),则不需要额外的 fetch,尽管有人可能会争辩说检索和存储用户可能永远不会看到的 cmets 效率低下。为了显示正确数量的 cmets,需要进行一些数学运算,但我稍后会介绍。

更新表格 - 在查看顶部单元格时,您将需要调用[tableView reloadData],因为将显示更多数据。根据数组是仅包含要显示的 cmets 还是包含与帖子相关的所有 cmets,您可能还需要调用上面的 fetch 方法。例如,如果您的数组仅包含 10 个 cmets,并且您需要提取额外的 10 个,那么您首先需要执行 fetch,当 fetch 完成时您需要调用reloadData。如果数组包含所有 56 个 cmets,那么您将需要更新 numCommentsToShow 属性,如下所述,然后立即调用 reloadData

更新触发器 - 查看顶部单元格时需要调用重新加载表格的方法;但是,您不想在视图首次出现时立即调用重新加载表。有很多方法可以实现这一点,这实际上取决于您是否希望在顶部单元格部分可见、完全可见甚至即将被查看时出现额外的 cmets(这实际上对用户来说可能是最干净的)。一种简单的实现(但不一定是最理想的)是在cellForIndexPath 方法中包含更新表的调用。如果indexPath.row 为 0,则调用更新。尽管在允许更新之前不只是加载视图,但您需要先测试一下。您还希望返回的单元格包含更新之前出现的注释,因为在获取之后将再次调用 cellForIndexPath 方法。如果您希望单元格在允许更新之前完全可见,您可以执行以下操作:Best way to check if UITableViewCell is completely visible

数学 - 如果数组只包含要显示的 cmets,我实际上会将 NSMutableArray 设为 NSArray。当您获得获取的结果时,我会将这 20 个 cmets 保存在一个全新的数组中,并让注释数组属性仅引用新数组。然后拨打reloadData。否则,在用户滚动时将 cmets 添加到 NSMutableArray 对我来说听起来有点危险,但我可能是错的。顺便说一句,有些人实际上可能只在接下来的 10 个 cmets(即 11-20)上执行提取,但如果您允许用户编辑他们的 cmets,我会检索 1-20,因为最近的 cmets 最有可能改变。提取后,数组将包含 20 个 cmets,它们应该按从最少到最近的顺序排列。这使得cellForIndexPath 方法变得容易,它应该只检索每个单元格的索引indexPath.row 处的注释。当前正在查看的单元格(更新前的同一单元格)将位于等于刚刚拉出的新项目数的索引处。

如果数组包含与帖子相关的所有 cmets(在我的示例中为 56),那么您需要一个包含要在此给定时间点显示的 cmets 数量的属性。当视图首次出现时,此属性设置为某个默认数字(在我的示例中为 10)。 numberOfRowsInSection 方法需要返回这个属性,我将其称为numCommentsToShow。但是,cellForIndexPath 需要检索数组中的正确项。如果它们从最近到最近排序,您实际上需要在索引处检索评论,该索引等于数组中 cmets 的数量减去numCommentsToShow 加上indexPath.row。这样做有点令人困惑。当您调用更新时,您需要将numCommentsToShow 增加一些增量(在我的示例中)10,但您应该小心不要将其增加到大于数组中的项目数。因此,您实际上应该将其增加增量数量和尚未显示的 cmets 数量中的最小值。

滚动问题 您可能会在滚动时遇到一些问题,因为表格顶部的单元格实际上会成为表格的中间单元格之一。因此,表格可能会在更新后跳到顶部。您可以做一些数学运算来确保设置更新后表格的滚动位置,以便在用户看来滚动位置没有改变,而实际上它已经改变。如果用户在表格尚未更新的情况下将表格向下移动,则会出现更困难的挑战。

在解释如何做到这一点之前,这些天的趋势似乎是避免这种情况。如果您查看 facebook 应用程序,则永远不会真正无限滚动到顶部。有一个按钮可以加载自动滚动到顶部的较新帖子。帖子上的 cmets 以最近最少的评论开头,您可以单击按钮“查看更多 cmets”以查看最近的评论。并不是说这是良好的用户体验,但是编写修复程序可能会花费更多时间而不是值得。

但无论如何,为了解决滚动问题,我会使用setContentOffset 方法,因为UITableView 继承自UIScrollView。您不应使用UITableViewscrollToRowAtIndexPath 方法,因为当前滚动位置可能未与任何给定行完全对齐。您应该确保将animated 参数设置为NO。您想要滚动到的确切位置涉及一些数学和时间。用户可能在获取数据时滚动到了新位置,因此您偏移了当前位置,而不是获取之前的任何位置。当提取完成并重新加载表时,您需要立即调用setContentOffset。新的 y 位置需要是当前滚动 y 位置 + 到新 cmets 的数量才能显示 * 行高。

虽然有点问题。当获取完成时,视图可能会处于滚动的中间。此时调用setContentOffset 可能会导致滚动跳转或停止。有几种方法可以处理这种情况。如果用户已经从表格顶部滚动,那么他/她可能不想再查看这些记录。因此,您可能会忽略获取的数据或将其保存在另一个地方,而不会将结果包括在表中。另一种是等到表格完成滚动后再使用新数据。第三种方法以与重新加载表格之前相同的速度和减速度继续滚动,但我不确定您将如何临时完成此操作。

对我来说,这比它的价值更痛苦。

希望这会有所帮助,

【讨论】:

谢谢。 “滚动问题”是我目前遇到更多困难的部分。【参考方案2】:

这是我在 Xamarin 中的解决方案,其中包含处理滚动问题的代码。我的标头是UIActivityIndicator,当没有更多内容要加载时将其删除。 DidScroll 是在UITableViewSourceScrolled 方法中触发的事件。 DidScrollEventArgs 包含从 Scrolled 方法传递的 UIScrollView

所以在UITableViewSource:

public class DidScrollEventArgs : EventArgs

    public UIScrollView scrollView;

    public DidScrollEventArgs (UIScrollView scrollView) : base ()
     
        this.scrollView = scrollView;
    

public event EventHandler<DidScrollEventArgs> DidScroll;
public override void Scrolled (UIScrollView scrollView)

    if (DidScroll != null)
        DidScroll.Invoke (this, new DidScrollEventArgs (scrollView));

在我的UIViewController:

source.DidScroll += async (object sender, MessageSource.DidScrollEventArgs e) => 

    nfloat height = e.scrollView.ContentSize.Height;
    nfloat contentYoffset = e.scrollView.ContentOffset.Y;

    if (contentYoffset == 0 && source.model.Count > 0 && height > MyBounds.Height && !isLoading && isMore) 
    
        isLoading = true;

        DateTime cutOff = source.model[0].SentDate.Value;

        //fetch more content
        List<MessageModel> newModels = await MessageAccess.GetMessagesAsync(ThreadId, cutOff, true);
        if (newModels.Count == 0)
            isMore = false;
            tableView.TableHeaderView = null;

            isLoading = false;
            return;
        

        //add it on to the front of the current content
        models = newModels.Concat(source.model).ToList();
        source.model = models;

        tableView.ReloadData();
        
        //calculate where to scroll to (new height - old height)
        nfloat yOffset = tableView.ContentSize.Height - height;

        //scroll there
        tableView.SetContentOffset(new CGPoint(0, yOffset), false);

        isLoading = false;

    
;

【讨论】:

以上是关于在 uitableview 上加载更多向上滚动的项目的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 在 reloadData 上向上滚动

懒惰地加载 NSTableView

每次我重新加载 tableview 部分时 UITableView 都不会向上滚动

UITableView 在像 Facebook 应用程序一样滚动到底部时加载更多

UITableView 防止滚动时重新加载

UITableView 仅在向上滚动时更新,而不是向下滚动