Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove UIButton's image for certain states?

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?

like image 204
Joel Derfner Avatar asked Feb 05 '13 02:02

Joel Derfner


5 Answers

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)
    }
}
like image 55
Minimistro Avatar answered Sep 21 '22 23:09

Minimistro


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.

like image 29
Mathew Avatar answered Oct 15 '22 03:10

Mathew


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];
like image 43
Kokaubeam Avatar answered Oct 15 '22 03:10

Kokaubeam


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];
        }
    }
}
like image 1
jianpx Avatar answered Oct 15 '22 05:10

jianpx


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.

like image 1
MultiColourPixel Avatar answered Oct 15 '22 05:10

MultiColourPixel