0

I have a header view for every UITableViewCell. In this header view, I load a picture of an individual via an asynchronous function in the Facebook API. However, because the function is asynchronous, I believe the function is called multiple times over and over again, causing the image to flicker constantly. I would imagine a fix to this issue would be to load the images in viewDidLoad in an array first, then display the array contents in the header view of the UITableViewCell. However, I am having trouble implementing this because of the asynchronous nature of the function: I can't seem to grab every photo, and then continue on with my program. Here is my attempt:

    //Function to get a user's profile picture
    func getProfilePicture(completion: (result: Bool, image: UIImage?) -> Void){
        // Get user profile pic
        let url = NSURL(string: "https://graph.facebook.com/1234567890/picture?type=large")
        let urlRequest = NSURLRequest(URL: url!)
        //Asynchronous request to display image
        NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
            if error != nil{
                println("Error: \(error)")
            }
            // Display the image
            let image = UIImage(data: data)
            if(image != nil){
                completion(result: true, image: image)
            }
        }
    }
    override func viewDidLoad() {
      self.getProfilePicture { (result, image) -> Void in
          if(result == true){
              println("Loading Photo")
              self.creatorImages.append(image!)
          }
          else{
              println("False")
          }
      }
    }

    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
      //Show section header cell with image
      var cellIdentifier = "SectionHeaderCell"
      var headerView = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! SectionHeaderCell
      headerView.headerImage.image = self.creatorImages[section]
      headerView.headerImage.clipsToBounds = true
      headerView.headerImage.layer.cornerRadius = headerView.headerImage.frame.size.width / 2
      return headerView
    }

As seen by the program above, I the global array that I created called self.creatorImages which holds the array of images I grab from the Facebook API is always empty and I need to "wait" for all the pictures to populate the array before actually using it. I'm not sure how to accomplish this because I did try a completion handler in my getProfilePicture function but that didn't seem to help and that is one way I have learned to deal with asynchronous functions. Any other ideas? Thanks!

user1871869
  • 3,317
  • 13
  • 56
  • 106
  • Your `viewForHeaderInSection` is not returning the view. – Rob May 20 '15 at 18:48
  • Oh sorry I left that part out there should be a return statement at the end. – user1871869 May 20 '15 at 19:42
  • OK. The "flickering" imageview is quite common when people asynchronously update their imageviews, but you're not doing that here (you're just asynchronously updating array). I see no reason why the header view images would flicker. I might advise `dequeueReusableHeaderFooterViewWithIdentifier` rather than `dequeueReusableCellWithIdentifier` (see http://stackoverflow.com/a/27843634/1271826), but I wouldn't have thought that would cause the problem you describe. I'm wondering if you're doing something with images in `cellForRowAtIndexPath`. I see nothing here that would cause the flickering. – Rob May 20 '15 at 19:47
  • @Rob Sorry, I forgot to mention that the code above is my updated version of my code. what I tried to do was to first put the `getProfilePicture` function into the `viewForHeaderInSection` function and as a result, that constantly calls the `getProfilePicture` function which causes it to flicker. Then, I tried another way which was to grab the photos beforehand in `viewDidLoad` which is what is shown in my code above. I am having issues now with grabbing the images in `viewDidLoad` and was wondering if people could help. I apologize for the misleading title. – user1871869 May 21 '15 at 19:22
  • OK, then I obviously cannot comment on the flickering. On the "prefetch" idea, I wouldn't recommend that (do you really want to freeze the app while it retrieves images or instead show what it can and have images pop in as they're downloaded). I'd rather see you have `viewForHeaderInSection` request the image it needs. The easiest way to do that is to use a `UIImageView` async image retrieval category as provided by SDWebImage or AFNetworking. – Rob May 21 '15 at 23:10
  • @Rob ah okay. I'll look into that. The thing is, why doesn't my `getProfilePicture` method work above? I'm making an asynchronous request and then using a completion handler to populate the array one by one and I just don't see why that wouldn't work. – user1871869 May 21 '15 at 23:12
  • Your table has already been populated when the request is done. So, you may be updating the array, but `viewForHeaderInSection` was already called. – Rob May 21 '15 at 23:35

1 Answers1

1

I had the same problem but mine was in Objective-C Well, the structure is not that different, what i did was adding condition with: headerView.headerImage.image

Here's an improved solution that i think suits your implementation.. since you placed self.getProfilePicture inside viewDidLoad it will only be called once section==0 will only contain an image,

the code below will request for addition image if self.creatorImages's index is out of range/bounds

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
      //Show section header cell with image
      var cellIdentifier = "SectionHeaderCell"
      var headerView = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! SectionHeaderCell


      if (section < self.creatorImages.count) // validate self.creatorImages index to prevent 'Array index out of range' error
      {
          if (headerView.headerImage.image == nil) // prevents the blinks
          {
              headerView.headerImage.image = self.creatorImages[section];
          }
      }
      else // requests for additional image at section
      {
          // this will be called more than expected because of tableView.reloadData()
          println("Loading Photo")

          self.getProfilePicture { (result, image) -> Void in
              if(result == true) {
                  //simply appending will do the work but i suggest something like:

                  if (self.creatorImages.count <= section)
                  {
                      self.creatorImages.append(image!)

                      tableView.reloadData()

                      println("self.creatorImages.count \(self.creatorImages.count)")
                  }
                 //that will prevent appending excessively to data source
              }
              else{
                  println("Error loading image")
              }
          }
      }

      headerView.headerImage.clipsToBounds = true
      headerView.headerImage.layer.cornerRadius = headerView.headerImage.frame.size.width / 2
      return headerView
    }

You sure have different implementation from what i have in mind, but codes in edit history is not in vain, right?.. hahahaha.. ;) Hope i've helped you.. Cheers!

0yeoj
  • 4,500
  • 3
  • 23
  • 41
  • I still get an error . I added that condition in the `viewForHeaderInSection` function and I still get an error saying `fatal error: Array index out of range`. I seem to be able to load the first image, but I can't load anything after the first image. Thanks though. Any other ideas? – user1871869 May 20 '15 at 18:38
  • Check the array count before trying to access the index – Leo Dabus May 20 '15 at 18:42
  • My answers was to remove the flickering/blinking.. about that fatal error.. you need to check the index of your array.. because you're working with async... i've edited my answer check it out.. – 0yeoj May 20 '15 at 18:45
  • So are you saying I just need a check to see if the index is populated? And that would fix everything? – user1871869 May 20 '15 at 18:51
  • technically yes.. because you have an error fatal error: `Array index out of range` describing it.. – 0yeoj May 20 '15 at 18:52
  • i need to go to sleep, its 3am here and i need to work later... i'll go back here later because i have quite the similar issue.. Good luck, cheers!.. – 0yeoj May 20 '15 at 19:01
  • @0yeoj hey thanks for helping me out. Sorry I got back to this so late, I was quite busy yesterday. I can't seem to check the Array index as I get an error saying that `an Int cannot be convertible to type Range`. I tried to add an if statement saying `if headerView.headerImage.image == nil && self.creatorImages[section] != nil` but that did not seem to work as I get that compilation error when I try to see if `self.creatorImages[section] != nil` – user1871869 May 21 '15 at 19:34
  • @LeonardoSavioDabus I tried your advice saying `if headerView.headerImage.image == nil && self.creatorImages.count > section` but that does not seem to work. I only get the first image and no other images are loaded. – user1871869 May 21 '15 at 19:36
  • @LeonardoSavioDabus It seems like the `viewForHeaderInSection` function is called before my asynchronous request of `getProfilePicture` finishes which causes me not to be able to get the contents inside `self.creatorImages`. Any ideas on how to fix this? I can't seem to check the array index either as said in my previous comment. – user1871869 May 21 '15 at 20:38
  • create a bool to only try to load the images if they are already finished and tableView reloadData when finished – Leo Dabus May 21 '15 at 20:42
  • Yes that is right.. you need to reload the table for every images that is loaded.. since you declared `var headerView` per cell.. – 0yeoj May 21 '15 at 22:19
  • Hey i tried your code and made it work properly.. :) – 0yeoj May 21 '15 at 23:09
  • @0yeoj oh wow so is it possible for you to share me the code? or give me any tips? I will try your method now and get back to you within a few minutes. – user1871869 May 21 '15 at 23:13
  • i edited my answer try it.. you dont really need to validate your `self.creatorImages`.. hmm.. and hey.. hahah.. i'm kind of late for work so.. later! Good luck to you sir.. Cheers! – 0yeoj May 21 '15 at 23:25
  • @0yeoj wow thank you for your help. I tried both methods and it was actually the first method that seemed to work with my implementation. The second also worked but seemed to call the `self.getProfilePicture` function over and over again which made no image load. However I really really appreciate your patience and help. Thank you! :) – user1871869 May 22 '15 at 00:12
  • Sorry, what i have in mind is, yeah totally different from yours.. i've edited my answer to suit yours(probably).. hahaha.. You are welcome.. :) – 0yeoj May 22 '15 at 03:52
  • A good first stab! A couple of problems, though: 1. If the cell has been reused and and another image was present in that old image, the old image will stay there until the new asynchronous request finishes. Set the image view's image to `nil` or some placeholder image before initiating asynchronous image retrieval. Failure to do so can yield a "flickering effect", momentarily showing the wrong image. – Rob May 22 '15 at 04:51
  • 2. You should test this, but I think calling `reloadData` to show the updated image is not a great idea, as it will refresh the whole table (possibly scrolling the user back to the top). 3. This perpetuates OP's bug that he incorrectly assumes images will be retrieved in the order that the requests were made. This may generally be the case, but cannot be relied upon (esp if images are different sizes). – Rob May 22 '15 at 04:51
  • 1
    Yes! That is right.. There are a lot of work actually for his code(sendAsynchronousRequest) to work perfectly, he must have a cache url and id for it so that it will not know the wrong image... alot of validation since he is not using external/ready made framework for that.. i'm also learning things here, so thank you for pointing things out.. :) – 0yeoj May 22 '15 at 05:06