3

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"
}
Ivan Cantarino
  • 3,058
  • 4
  • 34
  • 73
  • I would guess the issue is lazy load on images. To test this guess you would need to replace `post.user.profileImage` with some reasonably large image from your resources (any should do). Doing this pretty much all should be the same except image load is reduced. So please report if this removes the glitch. – Matic Oblak Apr 23 '18 at 10:52
  • add this 2 lines after deque your cell `cell.layer.shouldRasterize = true` `cell.layer.rasterizationScale = UIScreen.main.scale` or refer this link : https://stackoverflow.com/questions/18460655/uicollectionview-scrolling-choppy-when-loading-cells – Kuldeep Apr 23 '18 at 10:53
  • @MaticOblak let me test it out – Ivan Cantarino Apr 23 '18 at 10:54
  • @Kuldeep I'll add those lines to test – Ivan Cantarino Apr 23 '18 at 10:55
  • Note that `cell.layer.stuff` could be done only once in the init of the cell method (either `awakeFromNib()`, or `init(frame:style)`). Also, I think that `timeAgoDisplay()` could be optimized too. What's that code? Do you init a new dateFormatter each time in it? If yes, use the same each time (create a static/singleton one). That's small improvements, maybe not related to your whole issue. – Larme Apr 23 '18 at 10:56
  • @Larme I'll add the code within the `timeAgoDisplay()` function – Ivan Cantarino Apr 23 '18 at 10:57
  • @Larme creating a singleton for that function seems a good option though. It currently is a `Date() extension function` – Ivan Cantarino Apr 23 '18 at 10:59
  • Are you getting image with web Image URL? – Manish Mahajan Apr 23 '18 at 11:02
  • @ManishM.Mahajan the image is locally cached, was downloaded on a previous session. In this current session, I construct the Image from the cache database, then I construct the `Post` model, which in this state already has the image. – Ivan Cantarino Apr 23 '18 at 11:03
  • That's okay, I thought you used a `DateFormatter`. Comment the stuff related to `setBackgroundImageOffset(imageOffset:)` and or `userProfileImageView.image =`. Check if it's improved. What the size of theses images? – Larme Apr 23 '18 at 11:04
  • I don't know if this could be the issue or not. All my objects in the cell are `lazy` variables with the `lazy var get` constructor. I have done this in the past to only use the objects when needed, but since this is the main screen (the Home) tab it could be adding some overhead with those lazy initializations. I'll remove those `lazy` instantiations and Profile again – Ivan Cantarino Apr 23 '18 at 11:06
  • Removing the `lazy` constructor done nothing it still had those lag spikes and the Main Thread flies to 100%. – Ivan Cantarino Apr 23 '18 at 11:09
  • you are showing the images on cell and have you tried without setting images to cell ??? – Vikram Sinha Apr 23 '18 at 11:11
  • @vikramsingh I'm testing right now without setting the images inside the cell and I'll feedback soon ;) – Ivan Cantarino Apr 23 '18 at 11:12
  • @Larme testing it right now without some images in the cell, I'll feedback soon – Ivan Cantarino Apr 23 '18 at 11:12
  • How you cached image,I will suggest you to use SDWebImage to asynchronously download image and show in cell... – Manish Mahajan Apr 23 '18 at 11:18
  • @ManishM.Mahajan I cached it with `FMDB` for offline usage, in a binary file (NSData), then I construct it with the regular `UIImage(data:)` – Ivan Cantarino Apr 23 '18 at 11:19
  • Don't do anything just comment code where you set image... and looks whether it works or not smoothly – Manish Mahajan Apr 23 '18 at 11:20
  • Probably you need to use dispatch background and main queue.. like dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Add code here to do background processing // // dispatch_async( dispatch_get_main_queue(), ^{ // Add code here to update the UI/send notifications based on the // results of the background processing }); }); – Manish Mahajan Apr 23 '18 at 11:24
  • 1
    @ManishM.Mahajan I actually do all processing on background threads and when they're ready I dispatch to main. Now I have come to a conclusion that the problem is with the thumbnail images, I think those might be to big... I'm performing some tests again – Ivan Cantarino Apr 23 '18 at 11:26
  • Yes setting imageview is the only issue.. – Manish Mahajan Apr 23 '18 at 11:28

3 Answers3

2

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

Ladislav
  • 7,223
  • 5
  • 27
  • 31
  • wow... that could really be the issue here! I'll read it carefully so I can diagnose it properly – Ivan Cantarino Apr 23 '18 at 12:06
  • Try it, and you will see it will do wonders, also make sure the image size you use is as close to the final size as possible, so don't use 1000x1000 image for a small thumbnail because that will degrade scrolling performance... – Ladislav Apr 23 '18 at 12:10
  • 1
    that's another issue I have noticed as well, I think the thumbnail images are bigger than needed, I'll do some improvements there as well. Another thing I have noticed is that I was drawing with AutoLayout the cell's objects in the `layoutSubviews()` and not in the `init(frame:)`. That was causing me to redraw the whole cell's UI content without a reason to, because `layoutSubviews()` is called multiple times as we scroll. – Ivan Cantarino Apr 23 '18 at 12:12
  • Did the solution help? – Ladislav Apr 23 '18 at 14:49
1

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

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.

Vikram Sinha
  • 581
  • 1
  • 10
  • 25
  • I'll look into it. I'm currently running some tests trying to narrow down the issue and then I'll perform your suggestions. thank you – Ivan Cantarino Apr 23 '18 at 11:36
  • I have had the same issue and it helped me. It will cut your scroll load into more than half. I would recommend you. – Vikram Sinha Apr 23 '18 at 11:41