Question

So I have a list of items in a UICollectionView and there is a UIButton which has it's default and selected UIImage's set to a light grey heart and a black heart.

My aim is to allow users to add items to a list of favourites. I've added a column to the database called "favourite" and it is of type boolean.

Customer taps heart in cell of item. A method is triggered which users the object id of the item that is passed along as the titleLabel of the UIButton to grab the item from the database and update the boolean value of the favourite column to "True".

Method for favourite button:

- (void)addFavouriteButtonTapped:(UIButton *)sender
{
    NSLog(@"add to favourites button tapped %@", [[sender titleLabel] text]);

    // Set data base favaourite status to 1 (yes)
    PFQuery *query = [PFQuery queryWithClassName:@"Garments"];
    [query getObjectInBackgroundWithId:[[sender titleLabel] text] block:^(PFObject *object, NSError *error) {
        object[@"favourite"] = @YES;
        [object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
            if (!error) {
                [[[UIAlertView alloc] initWithTitle:@"Add Favouite"
                                            message:@"Item successfully added to favourites"
                                           delegate:_thisController
                                  cancelButtonTitle:@"Ok"
                                  otherButtonTitles: nil] show];
            } else {
                NSLog(@"Something went wrong");
            }
        }]; 
    }];   
}

Then in my cellForItemAtIndexPath I have an if statement which sets the selected state of the UIButton.

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

        NSLog(@"cellForItemAtIndexPath");
        // other cell info here deleted to make things clearer

         _addFavouriteButton = [cell addFavouriteButton];


        // if database favourites status is equal to one then set highlighted to (yes)
        if ([object valueForKey:@"favourite"] ==  [NSNumber numberWithBool:YES]) {
            [_addFavouriteButton setSelected:YES];
            NSLog(@"SETTING SELECTED");
        } else if ([object valueForKey:@"favourite"] ==  [NSNumber numberWithBool:NO]) {
            [_addFavouriteButton setSelected:NO];
        }

        [[_addFavouriteButton titleLabel] setText:[object objectId]];

        [_addFavouriteButton addTarget:_thisController action:@selector(addFavouriteButtonTapped:) forControlEvents:UIControlEventTouchUpInside];

        return cell;
}

Now when I leave the view and everything is destroyed then return back to it again, black hearts appear where ever I selected a favourite. This is fine. The problem I am having is when I actually make the selection of the item to add as a favourite the heart is updated and remains grey until I leave the UICollectionView and return.

I've tried reloadData in the main queue of the addFavouriteButtonTapped: method I've also tried reloading just the cell.

None of this works.

I can set the UIButton's state to selected but as I scroll the selection doesn't keep position and moves to other cell UIButton's and off the cell with the button I actually selected. Only way to fix this is to leave the few so the we have a fresh UICollectionView that uses the if statement from my database to determine whether a cell is selected or not.

**My custom cell:**

@interface VAGGarmentCell : UICollectionViewCell
@property (weak, nonatomic) IBOutlet PFImageView *imageView;
@property (weak, nonatomic) IBOutlet UIButton *addFavouriteButton;

@property (weak, nonatomic) IBOutlet UITextView *title;
@property (weak, nonatomic) IBOutlet UILabel *price;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;

@end

Question:

I've done the bulk of what needs to be done and can use the favourites column later on when building the page that lists items in the customers favourite list. But how do I refresh the UICollectionView right after the button was tapped and successful update in order to display the UIButton's selected state?

Was it helpful?

Solution

Make sure you also set the sender (ie the button that was pressed) to selected too, or reload the collection view once after updating your model.

Update: This answer has been edited and updated after a discussion in chat.

There isn't a need for an iVar (or @property) for _addFavouriteButton in your view controller, the button variable should just be a local variable in the scope of the collectionView:cellForItemAtIndexPath:object: method - lets start by fixing that method first

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
    NSLog(@"cellForItemAtIndexPath");
    // other cell info here deleted to make things clearer

    // Create a local variable for your button - this saves a bit of typing in the rest
    // of the method. You can also use dot notation as its a property
    UIButton *favouriteButton = cell.addFavouriteButton;

    BOOL objectFavourited = [object[@"favourite"] boolValue];
    NSLog(@"Setting selected? %@", objectFavourited ? @"YES" : @"NO");
    favouriteButton.selected = objectFavourited;

    // You should set the title directly on the button (as per the docs - 
    // https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIButton_Class/UIButton/UIButton.html#//apple_ref/occ/instp/UIButton/titleLabel), 
    // you shouldn't touch titleLabel except for styling

    // However you only used the title for seeing which object the favourite button tap was for,
    // so I've commented it out
    // [favouriteButton setTitle:[object objectId] forState:UIControlStateNormal];

    [favouriteButton addTarget:_thisController action:@selector(addFavouriteButtonTapped:) forControlEvents:UIControlEventTouchUpInside];

    return cell;
}

Now the buttons target method. Here you should be doing the following: updating your model, changing the button state and then saving the model. If the model successfully saves do your users care - no not really! If however the save fails, then the user needs to know, and the button and the model need to be reverted to their previous state.
I'm pretty sure that if the save fails, the object will be in the same state as before you attempted the save - with the favourite property set to YES. Even if it doesn't, setting it back to NO wouldn't really matter anyway.

The best way to get the indexPath of the item that the had its button pressed is to use a similar method to the one in this answer. Its the perfect way to work out the index path, no need for horrible tags!

Now we have the index path of the item, you don't need to query for that specific object - we get get it directly by using objectAtIndexPath:.

- (void)addFavouriteButtonTapped:(UIButton *)button
{
    // Method adapted for a collection view from the above answer
    CGPoint hitPoint = [button convertPoint:CGPointZero toView:self.collectionView]; 
    NSIndexPath *hitIndex = [self.collectionView indexPathForItemAtPoint:hitPoint];

    // Now get the object for this index
    PFObject *object = [self objectAtIndexPath:hitIndex];

    NSLog(@"add to favourites button tapped %ld - %@", (long)hitIndex.item, [object objectId]); 

    // Now update the model
    object[@"favourite"] = @YES;

    // And the button state
    button.selected = YES;

    // Now save the model to parse
    [object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {

        if (!succeeded) {
            // Reset the object back to its previous state
            object[@"favourite"] = @NO;
            // Reload the item to reflect the changes in the model
            [self.collectionView reloadItemsAtIndexPaths:@[hitIndex]];

            // Only bother the user if something bad happened
            [[[UIAlertView alloc] initWithTitle:@"Oops!"
                                        message:@"There was a problem please try again later"
                                       delegate:self
                              cancelButtonTitle:@"Dismiss"
                              otherButtonTitles:nil] show];
        }
    }];
}

You could reload the collection view, but as you've updated the view in that method there isn't really any need.
However, you should reload the collection view if the save failed as the button might now be being used for a different item in the collection view!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top