0

I'm trying to create a level select screen for a game I'm making which consists of a grid of circular buttons with level numbers in them. At any one time one button should be selected and displayed with a filled background instead of just an outline.

My initial implementation was to create a view which looked something like this:

@implementation LevelView

- (void)drawRect:(CGRect)rect { 
    int i = 1;
    for (int row = 0; row < NUM_ROWS; row++) {
        for (int col = 0; col < NUM_COLS; col++) {
            // Calculate frame of the circle for this level.
            if (i == selected) {
                // Use CoreGraphics to draw filled circle with text.
            }
            else {
                // Use CoreGraphics to draw outlined circle with text.
            }
            i++;
        }
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    for (UITouch *touch in touches) {
        // Get the level that the touch is in the circle of.
        int level = [self levelForPoint:[touch locationInView:self]];
        selected = level;
        [self setNeedsDisplay];
    }
}

@end

The problem with this is that the selecting and unselecting of the various buttons are not animated (fade in, fade out) like the circular buttons in the iOS7 lock screen or Phone app.

Is there a better/easier way that I can accomplish this?

DanielGibbs
  • 9,910
  • 11
  • 76
  • 121
  • 1
    Why not use a set of `UIButton`s and setting a `selectedImage` and a normal `image` and just switch between the `selected` property? – Majster Aug 11 '14 at 20:22
  • I'd prefer to draw the circle somehow than rely on an image for the circle, as this makes it more complicated when dealing with different screen resolutions (e.g. retina and non-retina). Is it easy to dynamically create and position a bunch of `UIButton`s within a view? – DanielGibbs Aug 11 '14 at 20:41
  • Yes it is. You can render the images programatically if you prefer by using `UIGraphicsBeginImageContextWithOptions` and then you can draw just as you would in `drawRect` or you prepare `image` and `image@2x` for the retina and non-retina screens. About creating the buttons: its very easy and then you lay them out in `layoutSubviews`. I can write an answer describing my solution if you'd like. – Majster Aug 12 '14 at 08:17
  • That would be much appreciated - thanks. – DanielGibbs Aug 12 '14 at 08:25

2 Answers2

1

In response to your request to fade button state: we subclassed UIButton and overrode the setHighlighted: method like so:

- (void) setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];

    if (highlighted) {
      [self setBackgroundColor:[UIColor whiteColor]];
    } else {
      [UIView beginAnimations:nil context:NULL];
      [UIView setAnimationDuration:.4f];
      [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
      [self setBackgroundColor:nil];
      [UIView commitAnimations];
    }
  }
CloakedEddy
  • 1,965
  • 15
  • 27
0

As promised here is my way of tackling the problem. Let's first create a grid of UIButtons in viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];

    for (NSUInteger i = 0; i < 10; ++i)
    {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.tag = i;
        [button setImage:[UIImage imageNamed:@"normal_image"] forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"selected_image"] forState:UIControlStateSelected];
        [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];

        //You can assign a button frame here or in viewWillLayoutSubviews or viewDidLayoutSubviews
        button.frame = CGRectMake(your...frame);

        [self.view addSubview:button];
        [buttons addObject:button]; //NSMutableArray ivar containing our buttons
}

And that takes care of placing our buttons. You can use 2 for loops to make a grid of your choice. As for the image I'm assuming that you're using pre-baked images (you can pre-bake for retina and non retina separately). If you want to render it programatically let me know and I will edit the post.

As for the selection logic we implement the selector buttonTapped:

- (void)buttonTapped:(UIButton *)sender
{
    for (UIButton *button in buttons) //Resets all selected states if there were any
        button.state = UIControlStateNormal;

    sender.state = UIControlStateSelected;

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Hey" message:[NSString stringWithFormat:@"You touches the %ldth button.", (long)sender.tag + 1] delegate:nil cancelButtonTitle:@"Cool" otherButtonTitles:nil]
    [alert show];
}

EDIT as for the animation purposes you can take a look at the link here or you can use an array of imageViews with attached UITapGestureRecognizers.

EDIT 2 A basic way on how to draw images for your buttons:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(buttonWidth, buttonHeight), NO, 0.0f); //Makes a graphics context that is not opaque and uses the screen scale (so you do not need to worry about retina or non retina)
[[UIColor redColor] set];
UIRectFill(CGRectMake(0.0f, 0.0f, buttonWidth, buttonHeight);

NSDictionary *drawingAttributes = @{NSFontAttributeName: [UIFont systemFontOfSize:15.0f], NSForegroundColorAttributeName: [UIColor blueColor]};

[@"Normal state" drawAtPoint:CGPointZero withAttributes:drawingAttributes];

UIImage *normalStateImate = UIGraphicsGetImageFromCurrentImageContext();

[[UIColor greenColor] set];
UIRectFill(CGRectMake(0.0f, 0.0f, buttonWidth, buttonHeight);

[@"Selected state" drawAtPoint:CGPointZero withAttributes:drawingAttributes];

UIImage *selectedImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

And then you would apply the images in the same way as I have written above. How to draw pretty images and whatnot is another topic and if you want help on that I suggest googling or asking another question. If you ever feel like changing the images you would redraw them and apply them again to your buttons.

Community
  • 1
  • 1
Majster
  • 3,611
  • 5
  • 38
  • 60
  • Looks good. I'd still prefer to do it without using images, as if I wanted variable button sizes then the image would scale and the line thickness would vary. – DanielGibbs Aug 12 '14 at 18:00
  • I have edited the answer and added some basic info on how to draw images programatically. There may be a syntax error somewhere since I just wrote it in browser. If the answer helped you please accept it. – Majster Aug 12 '14 at 20:53
  • It was helpful, but not quite what I was looking for. As mentioned in the question I was trying to make circular buttons with similar fading effects as the lock screen/phone buttons. – DanielGibbs Aug 12 '14 at 21:23
  • Well in that case you just need to change the way you draw you images. You can use `CGContextFillEllipseInRect` to draw a circle and use different blend modes to achieve transparency. When you are switching between images you simply animate the changes. – Majster Aug 13 '14 at 06:24
  • So there's no way to use the fading of the default `UIButton`s? If I touch a normal `UIButton` and then drag my finger off it, it fades back to the normal state and I was hoping that by using `UIButton`s here I could have the same effect. – DanielGibbs Aug 13 '14 at 07:49