0

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?

Grey_Code
  • 454
  • 1
  • 4
  • 20
LondonGuy
  • 10,778
  • 11
  • 79
  • 151

1 Answers1

1

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!

Community
  • 1
  • 1
Rich
  • 8,108
  • 5
  • 46
  • 59
  • This was the first thing I tried but as I scroll then return to the cell I selected the button on it is no longer selected. – LondonGuy Apr 26 '14 at 12:10
  • The problem is you are not doing anything UI wise till your query has finished, so you button won't be selected till the request comepletes. I'll put a note in my answer about this actually :) – Rich Apr 26 '14 at 12:13
  • So add the hitPoint code to the top of _addFavoute method? Then after successful update reload item at index path? I've tried this using your code and it doesn't work for me. – LondonGuy Apr 26 '14 at 12:35
  • For some reason reloading just doesn't seem to work. I've even tried [_collectionView reloadItemsAtIndexPaths:[_collectionView indexPathsForVisibleItems]]; – LondonGuy Apr 26 '14 at 12:43
  • @LondonGuy yep at the top on the `addFavouriteButtonTapped:` method. Actually can you post more code, like what is `object`? It should be an array but it seems not to be? – Rich Apr 26 '14 at 12:46
  • Object is the object associated with the current index path being dealt with. – LondonGuy Apr 26 '14 at 12:51
  • Are you using a Parse view controller subclass or something? The reason I ask is that `collectionView:cellForItemAtIndexPath:object:` doesn't seem to be a standard method? – Rich Apr 26 '14 at 13:19
  • Yep, it's not official but works exactly the same as their table view version. – LondonGuy Apr 26 '14 at 13:21
  • Ah ok, so how come you're querying the object again, instead of using `objectAtIndexPath:`? And then updating and saving that object then? – Rich Apr 26 '14 at 13:22
  • I'm querying it in order to update it. So from my table "Garments" I use the objects id to get the object then I update the boolean value. – LondonGuy Apr 26 '14 at 13:34
  • Yeah, but you can get the index path of the button and then use `[self objectAtIndexPath:hitIndex]` to get the object, then `object[@"favourite"] = @YES`, and then save? – Rich Apr 26 '14 at 13:37
  • I don't understand. Can we take this into a chat? This is how I often update objects. This is how it's done in the parse.com documentation. – LondonGuy Apr 26 '14 at 13:48
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/51495/discussion-between-rich-and-londonguy) – Rich Apr 26 '14 at 13:49