【问题标题】:UICollectionView scroll glitchy/laggyUICollectionView 滚动故障/滞后
【发布时间】:2018-10-03 09:24:09
【问题描述】:

在我的 iOS 项目中,我使用带有自定义单元格的常规 UICollectionView

该单元格接收一些属性,当设置其中一个对象时,我对该单元格执行一些 UI 更新。

虽然当我滚动时感觉滚动有点小故障,但我不知道如何改进我的代码以使其运行更流畅。

我已经运行了 Intruments 并运行了 Time Profiler,当这些滞后发生时,主线程 CPU 使用率达到 100%,如下图所示:

追踪仪器的使用百分比:

进一步追踪png_read_IDAT_data

现在是代码:

在我的UICollectionViewController cellForItem(:_)

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: homeCellId, for: indexPath) as! HomePostCollectionViewCell
    cell.post = self.posts[indexPath.item]
    cell.delegate = self
    cell.layer.cornerRadius = 5
    cell.layer.borderWidth = 0.7
    cell.layer.borderColor = UIColor.rgb(red: 204, green: 204, blue: 204).cgColor
    cell.layer.masksToBounds = true
    return cell     
}

在我的UICollectionViewCell 中,每当设置post 变量时,我都会执行以下UI 更新:

public var post: Post? {
    didSet {
        guard let post = self.post else { return }
        userProfileImageView.image = post.user.profileImage
        nameLabel.text = post.user.name
        titleTextView.text = post.title
        creationDateLabel.text = post.creationDate.timeAgoDisplay()

        thumbnailImageView.image = post.thumbnail
        if backgroundImageOffset != nil {
            setBackgroundImageOffset(imageOffset: backgroundImageOffset)
        } else {
            setBackgroundImageOffset(imageOffset: CGPoint.zero)
        }
    }
}

您能帮我追踪问题以及如何改进它吗?

谢谢。

编辑

我已经注释掉了 cell.post= 行,这样主线程 CPU 使用率下降到大约 10%,因此可以确保问题出在 post { didSet{} } 构造函数中。

你能看到任何有用的改进吗?

编辑 2

这里是timeAgoDisplay()函数的代码

func timeAgoDisplay() -> String {
    let secondsAgo = Int(Date().timeIntervalSince(self))

    let minute = 60
    let hour = 60 * minute
    let day = 24 * hour
    let week = 7 * day
    let month = 4 * week

    let quotient: Int
    let unit: String
    if secondsAgo < minute {
        quotient = secondsAgo
        unit = "second"
    } else if secondsAgo < hour {
        quotient = secondsAgo / minute
        unit = "min"
    } else if secondsAgo < day {
        quotient = secondsAgo / hour
        unit = "hour"
    } else if secondsAgo < week {
        quotient = secondsAgo / day
        unit = "day"
    } else if secondsAgo < month {
        quotient = secondsAgo / week
        unit = "week"
    } else {
        quotient = secondsAgo / month
        unit = "month"
    }

    return "\(quotient) \(unit)\(quotient == 1 ? "" : "s") ago"
}

【问题讨论】:

  • 我猜这个问题是图像的延迟加载。为了测试这个猜测,你需要用你的资源中的一些相当大的图像替换post.user.profileImage(任何应该做的)。除了减少图像负载外,几乎所有操作都应该相同。所以请报告这是否消除了故障。
  • 在你的单元格cell.layer.shouldRasterize = truecell.layer.rasterizationScale = UIScreen.main.scale后添加这2行或参考这个链接:stackoverflow.com/questions/18460655/…
  • @MaticOblak 让我测试一下
  • @Kuldeep 我会将这些行添加到测试中
  • @ManishM.Mahajan 我实际上在后台线程上进行所有处理,当它们准备好时我分派到主线程。现在我得出结论,问题出在缩略图上,我认为这些可能太大了......我正在再次进行一些测试

标签: ios swift uicollectionview uicollectionviewcell


【解决方案1】:

我也遇到过像你这样的问题,认为UIImage(data:) 可以在后台线程上使用,然后只需将结果图像应用到main 队列上的UIImageView

但事实并非如此,引用关于这个主题的一个很好的答案UIImage initWithData: blocking UI thread from async dispatch?

UIImage 在第一次实际使用/绘制之前不会读取和解码图像。要强制这项工作在后台线程上进行,您必须在执行主线程 -setImage: 调用之前在后台线程上使用/绘制图像。许多人认为这违反直觉。我在另一个answer 中相当详细地解释了这一点。

如上所述,链接中的答案表明您必须在使用它之前在后台线程上use/draw 图像。

这是我发现如何在后台线程上强制解压的示例:

Preload UIImage for super-smooth interaction. especially great if you use JPGs, which otherwise produce a noticeable lag on the main thread.

希望这会有所帮助:)

【讨论】:

  • 哇...这可能真的是这里的问题!我会仔细阅读,以便正确诊断
  • 试一试,你会发现它会创造奇迹,还要确保你使用的图像尺寸尽可能接近最终尺寸,所以不要将 1000x1000 的图像用于小缩略图,因为这会降低滚动性能...
  • 这也是我注意到的另一个问题,我认为缩略图比需要的要大,我也会在那里做一些改进。我注意到的另一件事是,我在layoutSubviews() 中使用AutoLayout 绘制单元格对象,而不是在init(frame:) 中。这导致我无缘无故地重绘整个单元格的 UI 内容,因为在我们滚动时会多次调用 layoutSubviews()
  • 解决方案有帮助吗?
【解决方案2】:

啊,我遇到了同样的问题,它试图在每次滚动时重新计算它的 layoutAttributes。为了解决这个问题,请尝试在您的自定义单元格类中覆盖 PreferredLayoutAttributesFitting,如下所示:

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        return layoutAttributes
    }

【讨论】:

  • 哇,这为我解决了问题!但是为什么!?
【解决方案3】:

您应该尝试应用缓存您的图像并相应地显示它们; 看看下面的类似问题和苹果文档 - :

How to use NSCache

A mutable collection you use to temporarily store transient key-value pairs that are subject to eviction when resources are low.

【讨论】:

  • 我会调查的。我目前正在运行一些测试,试图缩小问题范围,然后我会执行你的建议。谢谢
  • 我也遇到过同样的问题,这对我有帮助。它会将您的滚动负载减少一半以上。我会推荐你​​。
猜你喜欢
  • 1970-01-01
  • 2019-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-16
  • 1970-01-01
相关资源
最近更新 更多