2

I have a UITableView populated with cells either containing just a text label or a text label and a UIImage. When the app is run, images are downloaded from my server and stored on the device locally. Every time the app is run, the app checks for new images and downloads them. In my cellForRowAtIndexPath function, I load the image from the device storage on a background thread then update the UI on the main thread.

I don't know if I am doing this right but here is my code for displaying images inside my cellForRowAtIndexPath function:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        for image in self.postsImages {
            if ((image as! NSArray)[0] as! Int == postIDCurrent) {
                // there is an image associated with this post

                let postImage = UIImage(contentsOfFile: (currentArray.lastObject as? String)!)
                dispatch_async(dispatch_get_main_queue(), {
                    cell.postImageView.image = postImage
                    cell.postImageView.clipsToBounds = true
                    cell.postImageView.userInteractionEnabled = true
                    cell.postImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(PostsTableViewController.imageTapped(_:))))
                })
            }
        }
    })

The for and the if statements both just really check if there is an image associated with the current cell we are going to display.

I'm sorry if I'm not providing sufficient information. If you need anything more, please comment.

Anyways, my question is, what am I doing wrong with this code which causes my UITableView to scroll very slow?

Parth Saxena
  • 71
  • 10
  • 1
    Does the table scroll slowly or jerkily? First optimization I would do is make the `self.postsImages` a Dictionary instead of an Array. The lookup by int on Dictionary is way faster than iterating through the entire array (you aren't breaking the for loop after you find the image you want). And I'm not sure where `currentArray` is defined where you are getting the image string... – Putz1103 May 09 '16 at 17:01
  • To jump back onto the main thread, try using `dispatch_async` instead of `dispatch_sync`. – paulvs May 09 '16 at 17:04
  • @Putz1103 Sorry for not specifying. The table scrolls jerkily meaning that whenever a new cell comes on the screen, the table freezes then suddenly scrolls down. `currentArray` is the array that holds all the content for the current cell we are producing. It holds the **Post ID**, the **text for the label**, and also the **path to the image** on the device itself. The **image path** is the last object in the array. I will look into making `self.postsImages` a Dictionary. Sorry for not specifying all this information, but thank you for your response. – Parth Saxena May 09 '16 at 17:06
  • @paulvs Unfortunately, that did not fix the problem. Thanks for the response though. – Parth Saxena May 09 '16 at 17:16
  • you need to break the for loop after you find the image you are looking for, otherwise the loop will continue running on the main thread. – Chris Wood May 09 '16 at 17:19
  • @ChrisWood This did not turn out to be the problem but thanks for reminding me to break the `for` loop. – Parth Saxena May 09 '16 at 23:10

2 Answers2

4

Chances are that postImage is much larger than than the bounds of cell.postImageView, so the assignment on the main thread takes a noticeable amount of time to scale it down. Assuming that postImageView's bounds are foreseeable, scaling the image to the correct size in the background thread will be quite likely to sort this out. If you don't have a resizing function handy, this gist looks reasonable.

Alex Curylo
  • 4,744
  • 1
  • 27
  • 37
  • Thanks so much for this solution. This fixed the scrolling problem. **However**, there is a new small problem using this solution. If I scroll fast enough, the images in a cell will flash with the wrong image (the image for the cell that was previously viewable on the screen) then suddenly change back to the correct image. This isn't that big of a deal as it only occurs when the user scrolls fast, but it would be awesome if I could find a solution. Thanks so much for the response. – Parth Saxena May 09 '16 at 23:22
  • Should be able to sort that by assigning the image to nil in `prepareForReuse`. That's also a good place to empty text fields, abort network accesses, and o on. – Alex Curylo May 10 '16 at 00:54
  • Sorry I don't work much with UITableViews, what exactly is the `prepareForReuse`? Is it a function? Sorry if my question sounds silly... – Parth Saxena May 10 '16 at 00:58
  • 1
    It's a method of `UITableViewCell` you override to, well, prepare for reuse :) [Description and link to official docs here!](http://stackoverflow.com/questions/9362713/how-to-use-prepareforreuse-method) – Alex Curylo May 10 '16 at 04:33
  • It worked. Seriously, I really want to thank you for your help. I almost killed myself trying to fix this problem in the last three days, thanks :) – Parth Saxena May 10 '16 at 05:14
  • You're very welcome. The best thanks is to pass help along to the next person! – Alex Curylo May 10 '16 at 16:15
0

Try breaking your for loop:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
    for image in self.postsImages {
        if ((image as! NSArray)[0] as! Int == postIDCurrent) {
            // there is an image associated with this post

            let postImage = UIImage(contentsOfFile: (currentArray.lastObject as? String)!)
            dispatch_async(dispatch_get_main_queue(), {
                cell.postImageView.image = postImage
                cell.postImageView.clipsToBounds = true
                cell.postImageView.userInteractionEnabled = true
                cell.postImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(PostsTableViewController.imageTapped(_:))))
            })
            break
        }
    }
})
Chris Wood
  • 306
  • 3
  • 13