Question

I'm working on a card game. I want the card backs to show an image and the card fronts to show the card contents. I've gotten the image to show on the back, but I can't figure out how to clear it when it's selected. (All the run-this-code-when-it's-selected code is running, so I know it's not a question of not actually changing state.) Here's my code:

-(void)updateUI {
for (UIButton *cardButton in self.cardButtons) {
    Card *card = [self.game cardAtIndex:[self.cardButtons indexOfObject:cardButton]];
    [cardButton setTitle:card.contents forState:UIControlStateSelected];
    [cardButton setTitle:card.contents forState:UIControlStateSelected | UIControlStateDisabled];
    [cardButton setImage:[UIImage imageNamed:@"cardback.png"] forState:UIControlStateNormal];

//I've tried various permutations of the following three lines, but the image never disappears.

    [cardButton setImage:nil forState:UIControlStateSelected];
    [cardButton setImage:nil forState:UIControlStateSelected | UIControlStateHighlighted];
    [cardButton setImage:nil forState:UIControlStateNormal];

    cardButton.selected = card.faceUp;
    cardButton.alpha=(card.unplayable ? 0.3:1.0);
    [self.scoreLabel setText:[NSString stringWithFormat:@"Score: %d",self.game.score]];
}
}

Any thoughts about what I'm doing wrong?

Was it helpful?

Solution

I am guessing your problem is this (from the Apple Documentation for setImage:forState:):

In general, if a property is not specified for a state, the default is to use the UIControlStateNormal value. If the UIControlStateNormal value is not set, then the property defaults to a system value. Therefore, at a minimum, you should set the value for the normal state.

So, you cannot simply unset images for some states because the default image will get used.

I see a couple of solutions to this problem:

  1. Use a transparent image for the states you want to show through rather than nil
  2. Give your cards a UIImageView subview that you set to the card back image. If the card is face down, use setHidden: to show the subview. If the card is face up, use setHidden: to hide the subview. I would probably use the – addTarget:action:forControlEvents: method within your custom button class to register a custom method that shows or hides the card back subview.

Note that you could easily take option 2 a step further by displaying the front of the card in a subview as well and then you can easily animate a flip transition between those two subviews so that the card "flips" over when pressed.

OTHER TIPS

A quick way to create a transparent image for the other states, since nil will default to the value of UIControlStateNormal, is to allocate and assign an empty image:

UIImage *cardBackImage = [UIImage imageNamed:@"cardback.png"];
UIImage *cardFrontImage = [[UIImage alloc] init];
[cardButton setImage:cardBackImage forState:UIControlStateNormal];
[cardButton setImage:cardFrontImage forState:UIControlStateSelected];
[cardButton setImage:cardFrontImage forState:UIControlStateSelected|UIControlStateDisabled];

If you use setBackgroundImage, that will be fine. here is my code.

- (void)updateUI
{
    for (int i=0; i < self.cardButtons.count; i++) {
        Card *card = [self.game cardAtIndex:i];
        UIButton *cardButton = self.cardButtons[i];
        [cardButton setTitle:card.contents forState:UIControlStateSelected];
        [cardButton setTitle:card.contents forState:UIControlStateSelected|UIControlStateDisabled];
        cardButton.selected = card.isFaceup;
        cardButton.enabled = !card.isUnplayable;
        cardButton.alpha = card.isUnplayable ? 0.3 : 1.0;
        //handle flipcard backgroundimage changes;
        UIImage *backgroundImage = (!cardButton.selected) ? [CardGameViewController cardBackgroundImage] : nil;
        [cardButton setBackgroundImage:backgroundImage forState:UIControlStateNormal];
        [self updateScoreLabel:self.game.score];
        NSArray *matchCards = [self.game.matchCards lastObject];
        if (matchCards) {
            UIColor *color = [self getOneMatchColor];
            for (NSNumber *matchCardIndex in matchCards) {
                NSUInteger index = [matchCardIndex integerValue];
                UIButton *matchCardButton = self.cardButtons[index];
                if (color) matchCardButton.backgroundColor = color;
            }
            [self.game.matchCards removeLastObject];
        }
    }
}

The simplest solution I found to this was to check if the button was flagged up as selected. If not, apply a background image, else apply a null UIImage.

Late to the party, and I can't vote up MultiColour Pixel's answer, or comment on it, I guess because I'm new here and don't have any reputation. But that's indeed the best and simplest approach; no need for transparent images or additional views.

I'm taking the same course now, as the OP, and ran into the same problem. The solution is that when the button is selected, you still set the image for the unselected state (to nil):

UIImage *image = [UIImage imageNamed:@"cardback.png"];
// ...
[cardButton setImage:(card.isFaceUp ? nil : image) forState:UIControlStateNormal];

And you don't ever set an image for any other state.

In case you stink at Objective C like me. This is Sharon and MultiColourPixel's solution implemented in Swift.

func removeImageIfSelected(myButton: UIButton) {
    if myButton.isSelected {
        //set nil for .normal so there isn't a default value for Swift to use
        self.setImage(nil, for: .normal) 
    } else {
        self.setImage(UIImage(systemName: "checkmark.circle.fill"), for: .normal)
    }
}

You can use tags:

For example:

- (IBAction)btnClicked:(id)sender {

if([sender tag]==1){

//do something

}
if([sender tag]==2){

//do something else
}

make sure you set different tags for the buttons by going to Attributes Inspector and change the default value of 0 to something else...

Hope this helps...

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