iosobjective-cparse-platformpfimageviewpfcollectionviewcell

PFImageView not working on sublass of PFCollectionViewCell


I am having difficulty getting my subclass of PFCollectionViewCell to play nicely with PFImageView. I am using Storyboards to rig up my PA_ProfileCollectionViewCell.

All I am trying to do is load in an image (which I can get successfully from parse), and assign it to a PFImageView in my custom cell. I would appreciate any help/insights that could help me from bashing my computer with a baseball bat, and trashing my office in a slow-motion blaze of glory.

My desired outcome: To have a custom cell (subclass of PFCollectionViewCell) to be used by -collectionView:cellForItemAtIndexPath:object:

The problem: My custom property PFImageView *imageThumb (on my subclass of PFCollectionViewCell) does not respond to loadInBackground: or setFile: which are two methods on the PFImageView class.


Here is my code for clarity:

- My custom PFCollectionViewCell: ProfileCollectionViewCell

@interface PA_ProfileCollectionViewCell : PFCollectionViewCell
@property (weak, nonatomic) IBOutlet PFImageView *imageThumb;
@end

- Register the custom class in viewDidLoad

- (void)loadView {
    [super loadView];
    [self.collectionView registerClass:[PA_ProfileCollectionViewCell class]
            forCellWithReuseIdentifier:_cellIdentifier];
}

- Setup the cell in collectionView:cellForItemAtIndexPath:object
This is where the main problem is occurring. My value for cell is successfully being painted darkGray, per the cell.backgroundColor: call, but all attempts to assign the image to my imageThumb are failing. Notice in the source code below that my imageThumb doesnt respond to the selectors loadInBackground or setFile, which comes stock with PFImageView.

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
                                  object:(PFObject *)object {


    PA_ProfileCollectionViewCell *cell = (PA_ProfileCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:_cellIdentifier
                                                                                                                   forIndexPath:indexPath];
    cell.backgroundColor = [UIColor grayColor];

    // Load the photo
    PFFile *pingThumb = [object objectForKey:@"imageMediumFile"];
    if (pingThumb){

        BOOL canLoadInBackground = [cell.imageThumb respondsToSelector:@selector(loadInBackground:)];
        BOOL canSetFile = [cell.imageThumb respondsToSelector:@selector(setFile:)];

        NSLog(@"can cell.imageThumb loadInBackground? : %hhd", canLoadInBackground);
        NSLog(@"can cell.imageThumb setFile? : %hhd", canSetFile);

        [cell.imageThumb setFile:pingThumb];
        [cell.imageThumb loadInBackground:^(UIImage *image, NSError *error) {
            if(!error){
                NSLog(@"CODE WON'T REACH THIS POINT.");
                NSLog(@"loadInBackground doesn't exist on cell.imageThumb)");
                [UIView animateWithDuration:0.2f animations:^{
                    cell.imageThumb.alpha = 1.0f;
                }];
            }
        }];

    }

    return cell;
}

The output of all the NSLog() statements in the code snippet above is:

can cell.imageThumb loadInBackground? : 0 (NO)
can cell.imageThumb setFile? : 0 (NO)

Other things I have tried:

I have picked through the source code on the ParseUIDemo, but all their implementations use the stock PFCollectionViewCell without any customizations or subclassing, so I tried adapting my project to use their approach, but I could never get things working there either.

I have read through this answer, on SO, which lead me to try crawling through my cell.contentViews.subviews and cell.subviews looking for a PFImageView Neither attempt was successful:

// [DIDN'T WORK]
for (UIView *subview in cell.contentView.subviews) {
    if ([subview isKindOfClass:[PFImageView class]]) {
        NSLog(@"Yay! I found a PFImageView"); // Never gets called
        break;
    }
}

I then tried this answer on SO, and that made me try grabbing the instance of PFImageView from my storyboard using a tag, which was equally unsuccessful.

PFImageView *photoView = (PFImageView *)[cell.contentView viewWithTag:242];
if(!photoView) NSLog(@"Can't find PFImageView with tag in Storyboards");
// -> Can't find PFImageView with tag in Storyboards

Lastly, I fed every feasible combination of classNames to all the methods that require classNames in the PFQueryCollectionViewController. For example: I tried the below lines of code where I swapped out all instances of CLASSNAMEHERE with PA_ProfileCollectionViewCell, UICollectionViewCell and PFCollectionViewCell, all with equal amounts of FAIL:

// the cell (with casting)
CLASSNAMEHERE *cell = (CLASSNAMEHERE *)[cv dequeueReusableCellWithReuseIdentifier:...];

// the cell (without casting)
CLASSNAMEHERE *cell = [cv dequeueReusableCellWithReuseIdentifier:...];

// registering the class
[self.collectionView registerClass:[CLASSNAMEHERE class]
        forCellWithReuseIdentifier:_cellIdentifier];

Update #01:

Per the suggestion from the user @soulshined I tried skipping the setFile: call, since it doesn't appear in the API via the Parse docs (but I never got an error, or an unrecognized selector error when using it), so I tried the below code snippet, with no luck (notice how the setFile: has been replaced with .file:

cell.imageThumb.file = pingThumb;
//[cell.imageThumb setFile:pingThumb];
[cell.imageThumb loadInBackground:^(UIImage *image, NSError *error) {

    if(!error){
        NSLog(@"CODE WON'T REACH THIS POINT.");
        NSLog(@"loadInBackground doesn't exist on cell.imageThumb)");
        [UIView animateWithDuration:0.2f animations:^{
            cell.imageThumb.alpha = 1.0f;
        }];
    }
}];

Update #02

Again, per the suggestion of @soulshined, I used the advice give here, and changed my PFImageView *imageThumb to a UIImageView *imageThumb, and tried assigning the NSdata returned from getDataInBackground:withBlock: to the image instead, here is my code snippet (Note that I do get the "Got Data!" output to the console, but the images are still not showing up):

// pingThumb is a PFFile
[pingThumb getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
        if (!error) {
            NSLog(@"Got data");
            UIImage *image = [UIImage imageWithData:data];
            [cell.imageThumb setImage:image];
        }
    }];

So can anyone think of a step that I am missing? Why is it so difficult to get a PFImageView to load with a subclass of PFCollectionViewCell?

Thanks in advance for anyone taking the time to help me out!


Solution

  • The problem you are facing is that you are registering the class in your -viewDidLoad method, but you are setting up your cell via the Prototype cell in Storyboard.

    If you refer to the "Cell and View Reuse" section here, you will read (emphasis mine):

    ... if the cell or supplementary views are created using prototypes within a storyboard it is not necessary to register the class in code and, in fact, doing so will prevent the cell or view from appearing when the application runs.

    This is why your PFImageViews were not behaving, because you were breaking the Prototype connections you made in Storyboards after -registerClass:forCellWithReuseIdentifier: was called, and your PFImageViews were lost in runtime limbo.

    So, to solve your problem, simply REMOVE -registerClass:forCellWithReuseIdentifier from your view controller.

    Et voila, problem solved...

    So this code below is not needed anywhere in your view controller, because again, if you are using prototype cells, there is no need to register the class:

    // Not needed when using Prototype cells in Storyboards!
    [self.collectionView registerClass:[PA_ProfileCollectionViewCell class]
                forCellWithReuseIdentifier:_cellIdentifier];
    

    Once you remove the registerClass code, the code you have working with your PFImageView in your question's code examples above should work just fine.

    Hope this helps!


    On a side note, there is no need to cast your custom cell when instantiating it in -collectionView:cellForItemAtIndexPath:object

    // this is all you need
    SomeCustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:_cellId]; 
    
    // the casting example below is redundant and unnecessary, stick with the top example 
    SomeCustomCell *cell = (SomeCustomCell *)[collectionView dequeueReusableCellWithReuseIdentifier:_cellId];