在UITableViewCell中执行耗时的任务,暂停滚动

时间:2018-03-18 10:32:35

标签: swift uitableview asynchronous prefetch

我有TableView与海关单元代表事件。它看起来非常接近第一和第三张图像。

enter image description here

正如您在某些活动中所看到的(对不起小分辨率),会有朋友的照片参加。 不幸的是,关于朋友的信息没有加载有关事件的其他信息。

因此,在我获得事件列表后,我可以请求加载将参与每个事件的朋友列表。

现在我使用像

这样的代码
class EventCell : UITableViewCell {
  var eventForCell : Event? {
      didSet {
          eventTitleLabel.text = eventForCell.title
          eventDateLabel.text = eventForCell.date
          presentFriends(eventID : eventForCell.id)
      }
   }

   func presentFriends(eventID : Int) {
        //searching for friends for event with specific eventID
        .......
        .......

        //for every friend found adding UIImageView in cell
        for friend in friendsOnEvent {
          let avatar = UIImageView(....)
          self.addSubview(avatar)
        }
   }
}

此代码有效,但照片不能以流畅的方式呈现。此外,如果您快速滚动列表,它们会开始闪烁。如果用户滚动快速的事件列表,甚至可能没有必要加载它们。所以我有两个问题:

  1. 如何考虑到为每个事件呈现朋友需要花费时间,有时在单元格滚动后完成,我如何能够实现平滑的滚动体验。
  2. 如果我已经加载了事件列表并且已经使用它们呈现了单元格。在获得有关参与的朋友的信息后,如何更新这些单元格?
  3. 当用户滚动并且我正在创建异步任务以在单元格中显示一些图像时我认为我应该使用弱引用自我并且可能检查它不等于nil因此如果单元格现在不可见则任务将被取消。应该怎么做?
  4. 更新

    我在UITableViewPrefetchingDataSource协议中找到了关于 tableView(_:prefetchRowsAt:)方法的信息,我应该在这种情况下使用它吗?也许有人有一些经验吗?

2 个答案:

答案 0 :(得分:2)

1。(重新)在cellForRowAt期间创建视图对象通常是一种不好的做法。从屏幕截图中我假设单个单元格上有多少个化身有限制 - 我建议在单元格初始化程序中创建所有UIImageView个对象,然后在presentFriends中将图像设置为它们,或隐藏未使用的(isHidden = true)或将其alpha设置为0(当然,不要忘记取消隐藏使用的那些)。

2. 如果您正在使用SDWebImage加载图片,请实施prepareForReuse并取消当前下载以在滚动期间获得一些性能提升,并防止未定义的行为(当您尝试设置另一个图像,而前一个图像尚未下载)。基于this questionthis onethis one,我希望如下:

    override func prepareForReuse() {
        super.prepareForReuse()

        self.imageView.sd_cancelCurrentImageLoad()
    }

 Or you can use [this gist][4] for an inspiration.

P.S。:此外,由于图像是从网上下载的,因此您将不得不指望一些闪烁 - 总会有一些延迟。通过缓存,您可以立即获得已下载的内容,但新内容会有延迟 - 无法阻止(除非您在呈现tableView之前预加载tableView中可能出现的所有图像)。

P.S.2:您可以尝试使用[UITableViewDataSourcePrefetching][6]实现预取。这可以帮助您解决因从网上下载头像而导致的闪烁问题。这会让事情变得复杂一点,但是如果你真的想要消除这种眨眼,你将不得不弄脏你的手。

首先,正如您从文档中看到的那样,prefetchRowsAt没有为您提供单元格对象 - 因此您必须将图像预取到模型对象,而不是简单地使用sd_setImage给定单元格的UIImageView对象。也许前面提到的gist可以帮助您将图像下载到模型中。

现在也是documentation州:

  

异步加载数据

     

不一定要为表视图中的每个单元调用tableView(_:prefetchRowsAt:)方法。因此,您tableView(_:cellForRowAt:)的实施必须能够应对以下潜在情况:

     

已通过预取请求加载数据,并准备好显示。

     

目前正在预取数据,但尚未提供。

     

尚未请求数据。

简单地说,当你在cellForRowAt时,你不能依赖prefetchRowsAt被调用 - 它可能已被调用,它可能没有被调用(或者可能正在调用)

文档继续建议使用Operation作为下载资源的后备工具 - prefetchRowsAt可以创建Operation个对象,负责下载给定行的头像。然后cellForRowAt可以向模型询问给定行的Operation个对象 - 如果没有,则表示没有为该行调用prefetchRowsAt,并且您必须创建{ {1}}此时的对象。否则,您可以使用Operation创建的操作。

无论如何,如果您认为值得,可以使用此tutorial作为实现预取的灵感。

答案 1 :(得分:0)

如您所述,您可以使用UITableViewDataSourcePrefetching

这是一个协议,当某些单元格将要显示但尚未出现在屏幕上时,它会调用您的预取数据源。
通过这种方式,您可以准备在呈现之前需要花费时间加载的所有资源。

你必须实施:

func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])

并从所有indexpaths获取与单元格相关的数据。

请注意它仅在 iOS10 之后才可用。

我个人使用带有缓存的AlamofireImage,因此已经下载的图像不会被提取两次,有很多替代方案,但在这种情况下使用缓存图像是一种很好的做法。