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

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

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

【问题讨论】:

    标签: ios uitableview


    【解决方案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;
    
        }
    };
    

    【讨论】:

      猜你喜欢
      • 2016-04-02
      • 1970-01-01
      • 1970-01-01
      • 2017-05-06
      • 2014-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多