0

In my app I'm using UICollectionView and I've decided to use it as in the code below:

class UserList: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

@IBOutlet weak var tview: UICollectionView!
let reuseIdentifier = "cell" 
var items = NSMutableArray()

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    let cell = tview.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCollectionViewCell

    let user:SingleUser =  self.items[indexPath.item] as! SingleUser   

    cell.username.text = user.name


        if let checkedUrl = NSURL(string: user.photo) {
            cell.userImg.contentMode = .ScaleAspectFit

            getDataFromUrl(checkedUrl) { (data, response, error)  in
                dispatch_async(dispatch_get_main_queue()) { () -> Void in
                    guard let data = data where error == nil else { return }
                    print(response?.suggestedFilename ?? "")
                    cell.userImg.image = UIImage(data: data)
                }
            }

        }

    return cell
}

So I have a cell with a UILabel and UIImage and for each object fetched from json I parse it and I assign user's data to that cell.

When I load the window I see usernames with photos of them, but when I start scrolling through the collection view the photos of users change and users get different photos than they should have.

I've read it might be something related to cell reusing (so far I know nothing about it), I just assumed it should be loaded once (when user opens this panel) and then left as it is. However it seems like this data is "fetched" each time user scrolls that list.

So how can I exactly fix my problem and be sure each time user scrolls the list - the data will be correct?

One more thing that might be useful here - method getDataFromUrl that fetches photos from urls:

func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
    NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
        completion(data: data, response: response, error: error)
        }.resume()
}
user3766930
  • 5,629
  • 10
  • 51
  • 104
  • 1
    yes, you're right, it's happening cause you're reusing the cells. Also you're doing async image fetch, so I assume that is the problem cause the cellForItemAtIndexPath is done executing by the time you're assigning the image. You can try to request your images upfront, and store them in the array or something (cache), then load them in the cellForItemAtIndexPath from that cache. – Eugene Gordin Apr 08 '16 at 23:06
  • Hmm that's a completely different approach that I didn't think about, but your comment gave me some light in a tunnel. However, I don't know yet how exactly should I do it, could you help me with that and show some brief example? – user3766930 Apr 09 '16 at 10:23

1 Answers1

1

What Eugene said is correct, but if you don't want to change the code you have already too much. You can add an optional UIImage property on SingleUser, call it "image," then you can say something like:

if user.image == nil{
  getDataFromUrl(checkedUrl) { (data, response, error)  in
    dispatch_async(dispatch_get_main_queue()) { () -> Void in
     guard let data = data where error == nil else { return }
     print(response?.suggestedFilename ?? "")
     let image  = UIImage(data: data)
     user.image = image
     //Check if this cell still has the same indexPath, else it has been dequeued, and image shouldn't be updated
     if collectionView.indexPathForCell(cell) == indexPath
     {
       cell.userImg.image = image
     }
    }
   }
  }else{
    cell.userImg.image = user.image!
  }
}
beyowulf
  • 15,101
  • 2
  • 34
  • 40
  • in your example this statement `where error == nil else { return cell }` causes error `Unexpected non-void return value in void function` - do we need a `cell` here? (with normal `{ return }` it works ok, however I have a problem now that the data is not fetched before the list appears, so the `collection view` does not show images. – user3766930 Apr 09 '16 at 11:33
  • I updated my answer. By data you mean? Image data? Do you know it's not being fetched? I meant to say that UIImage property on SingleUser should be optional, perhaps that's part of the problem. – beyowulf Apr 09 '16 at 12:34
  • 1
    Right, don't initialize, just declare it as optional. i.e. simply `var image: UIImage?` That way it will initialize to nil then enter if user.image == nil. – beyowulf Apr 09 '16 at 16:17
  • Hm, now it works, partially, because okay, the images don't jump around the cells and are sticked to it, but during opening the panel cells have assigned different photos - for example cell no. 1 get a photo from user no. 3, cell no. 2 gets image from user 1, etc. Seems like some multithreading issue... – user3766930 Apr 09 '16 at 16:49
  • Also, you wrote this `if` statement: `if collectionView.indexPathForCell(cell) == indexPath`. But what if it changed? Why there isn't any `else` statement here? Sorry for lots of questions and thanks for your patience! – user3766930 Apr 09 '16 at 17:10
  • 1
    I don't know what panel cells are. Maybe I don't understand what your problem is. But I thought you were doing an asynchronous fetch of an image. When that happens the cell is displayed, but the image is still being fetched. So the user could swipe on the collection view. If they swipe the cell off the screen that cell will be dequeued and be given a new indexPath. The if statement will check if the cell's current indexPath is the same as before as when the fetch was initiated. If it's not, that cell is not onscreen, so we don't have a cell to assign it to, so there's nothing else to do. – beyowulf Apr 09 '16 at 17:34
  • Is SingleUser a struct or a class? – beyowulf Apr 09 '16 at 18:19
  • hmm I think I got the solution now, I did some tests and your answer was really helpful, especially the long comments - it helped me to understand how exactly the collection view works on ios. Thank you very much for your effort and patience, cheers! – user3766930 Apr 10 '16 at 13:08