Ivan Cantarino
Ivan Cantarino

Reputation: 3246

UICollectionView scroll glitchy/laggy

In my iOS project I use a regular UICollectionView with a custom cell.

That cell receives some properties and when one of those objects is set I perform some UI updates to the cell.

Though when I scroll I feel a slight glitchy scroll but I don't know how to improve my code in order to make it run smoother.

I have ran Intruments and ran a Time Profiler, when those lags occur the Main Thread CPU usage reaches 100%, as you can see in this image:

main thread spikes

Tracing the percentage usage in instruments it came to this:

trace 1

Tracing further into the png_read_IDAT_data

trace 2

Now to the code:

In my 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     
}

and inside my UICollectionViewCell whenever the post variable is set I perform the following UI updates:

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)
        }
    }
}

Can you help me tracing the issue and how it can be improved?

Thank you.

EDIT

I have commented out the cell.post= line and by doing that the Main Thread CPU usage went down to about 10%, so one can ensure that the problem is inside the post { didSet{} } constructor.

Can you see any useful improvements to be done there?

Edit 2

As requested here's the code of the timeAgoDisplay() function

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"
}

Upvotes: 3

Views: 2895

Answers (3)

Rhys Kentish
Rhys Kentish

Reputation: 41

Ah I had this exact same problem, it is trying to recalculate its layoutAttributes every time it scrolls. To combat this try overriding in your custom cell class preferredLayoutAttributesFitting like so:

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

Upvotes: 1

Ladislav
Ladislav

Reputation: 7283

I have faced problems like yours as well, thinking that UIImage(data:) can be used on the background thread and then just apply the result image to the UIImageView on the main queue.

That however is not the case, quoting from a great answer on this topic UIImage initWithData: blocking UI thread from async dispatch?:

UIImage doesn't read and decode the image until the first time it's actually used/drawn. To force this work to happen on a background thread, you have to use/draw the image on the background thread before doing the main thread -setImage: call. Many folks find this counter-intuitive. I explained this in considerable detail in another answer.

As above suggests and the answer in the link demonstrates you will have to use/draw the image on the background thread before using it.

Here is an example I found how you can force decompression on the background thread:

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

Hope this helps :)

Upvotes: 3

Vikram Sinha
Vikram Sinha

Reputation: 609

You should try applying the cache your images and show them accordingly; have a look to the similar question below and apple documentation -:

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.

Upvotes: 0

Related Questions