How to remove UIButton's image for certain states?
Asked Answered
B

7

10

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?

Bandanna answered 5/2, 2013 at 2:13 Comment(1)
Paul Hegarty awesome :DArmyworm
F
6

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.

Forewarn answered 5/2, 2013 at 3:58 Comment(0)
L
6

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];
Lucullus answered 9/3, 2013 at 0:4 Comment(3)
I never seen UIImage *cardFrontImage = [UIImage alloc]; why we don't made init? Is there not a mistake? I'm beginner, could you explain? Thanks. And why we can't made this: UIImage *cardFrontImage = nil;Armyworm
also, it helped me. I think UIImage *cardFrontImage = [[UIImage alloc] init]; is a proper way. Paul Hegarty by the way, said many times, that alloc never used without init.Armyworm
UIImage *cardFrontImage = nil; can't be used because it would be the same as [cardButton setImage:nil forState:UIControlStateSelected]; and nil values default to the value used by UIControlStateNormal. By giving it an instantiated UIImage, it will "draw" this image instead even though it's drawing an image with no image data.Lucullus
C
1

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];
        }
    }
}
Corney answered 28/3, 2013 at 3:55 Comment(0)
S
1

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.

Shortcoming answered 11/4, 2013 at 13:57 Comment(0)
S
1

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.

Semicentennial answered 1/7, 2013 at 20:56 Comment(0)
S
1

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)
    }
}
Slant answered 11/11, 2021 at 19:41 Comment(0)
W
0

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...

Wentz answered 5/2, 2013 at 2:56 Comment(1)
Thanks, but the problem isn't that the image is being removed or not removed for the wrong buttons--it's that the image isn't being removed for ANY of the buttons, no matter what their state. :(Bandanna

© 2022 - 2024 — McMap. All rights reserved.