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?
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];
}
}
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.
Related
I'm making a custom view to display a simple star rating system in my app. Each star is a UIButton that has a specific star image-file set as its image. Everything works fine in storyboard but when I run the app on a device each star image is slightly transparent.
When I set the background color of the buttons themselves, they are 100% opaque yet the images are still slightly transparent. Any reason why that same button's image wouldn't be opaque? I just want the stars to always be opaque and not dependent on background.
I just joined SO so I don't have enough reputation to post images yet but here's a link to an image that might explain the issue a bit better: photo
Basic code for the custom UIView:
- (void)layoutSubviews
{
[super layoutSubviews];
self.opaque = YES;
[self drawStars];
}
- (void)drawStars {
float imagePadding = self.padding;
CGFloat starWidth = self.selectedStarImage.size.width;
for (NSInteger counter = 0; counter < self.totalStars; counter++)
{
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(counter * (imagePadding + starWidth), 0, starWidth, starWidth)];
button.opaque = YES;
if (counter >= self.rating) {
[button setImage:self.starImage forState:UIControlStateNormal];
}
else {
[button setImage:self.selectedStarImage forState:UIControlStateNormal];
}
[self addSubview:button];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
button.tag=counter;
}
}
- (void)updateStars
{
for (UIView *subview in self.subviews) {
[subview removeFromSuperview];
}
[self drawStars];
}
Any help would be much appreciated.
It could happen that the alpha of view on which you are adding the buttons as subviews could be lesser than one.
i think you have to first change the image of star because its background it transparent that is the problem and check the background color of button.
Or
you can create whole transparent image and set proper background color which you want.
These images are not proper. or check it by using another images.
Hey so I have created two buttons that when pressed transition the current scene to either the next scene or the main menu and they work but neither button disappears after it has been pressed. They both carry over onto the next scene which I don't want. I've tried hiding the buttons and removing them from the superView but neither worked. Heres what the code looks like for what happens when it is pressed.
-(IBAction)buttonPressed:(UIButton *)sender
{
if ([sender isEqual:self.continueButton]) {
SKTransition* reveal = [SKTransition doorsCloseVerticalWithDuration:0.5];
SKScene* transition2Scene = [[Transition2Scene alloc] initWithSize:self.size];
[self.view presentScene:transition2Scene transition: reveal];
[self.continueButton removeFromSuperview];
self.continueButton.hidden = YES;
self.continueButton.enabled = NO;
}
else {
SKTransition* doors = [SKTransition doorsCloseVerticalWithDuration:0.5];
SKScene* spriteMyScene = [[SpriteMyScene alloc] initWithSize:self.size];
[self.view presentScene:spriteMyScene transition: doors];
self.menuButton.hidden = YES;
self.menuButton.enabled = NO;
[self.menuButton removeFromSuperview];
}
}
And heres what the coding of the buttons themselves looks like.
self.continueButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.continueButton.frame = CGRectMake(self.frame.size.width/3, self.frame.size.height * 0.5, 120, 70);
[self.continueButton setTitle:#"Continue" forState:UIControlStateNormal];
[self.continueButton addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.continueButton];
self.menuButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.menuButton.frame = CGRectMake(self.frame.size.width/3, self.frame.size.height * 0.6, 120, 70);
[self.menuButton setTitle:#"Main Menu" forState:UIControlStateNormal];
[self.menuButton addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.menuButton];
I've looked around for answers and tried some things but they didn't work cause the issue I'm having isn't quite the same so any help would be greatly appreciated thanks!
The SKScene and its contents are remove from the view when transitioning to a new scene. Views, such as your buttons, are not removed. You will need to manually remove them. Here's an example...
- (IBAction) buttonPressed:(UIButton *)sender
{
[self.continueButton removeFromSuperview];
[self.menuButton removeFromSuperview];
// Find and remove all UIButtons in your view
for (UIView *view in self.view.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
NSLog(#"%#", view);
[view removeFromSuperview];
}
}
// Verify that the UIButtons were removed
for (UIView *view in self.view.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
NSLog(#"this shouldn't happen: %#", view);
}
}
// The rest of your code
Alternatively, you can create buttons using SKSpriteNode and add them to your scene. Here's an example...
Setting up buttons in SKScene
I'm trying to write some methods which can be used to generate labels, images, buttons etc.. in my spritekit project.
I call my setUPScene method in the initWithSize method and in the setUpScene method I call three methods to set label, image and button.
My label and image appears on the main scene but I can't succeed to appear UIButton type button. Actually I seek for some error but I think everything seems right. Here are my methods;
-(void) setUpMainScene {
self.backgroundColor = [SKColor colorWithRed:1.0 green:0.15 blue:0.3 alpha:1.0];
[self addChild:[self createLabel:#"Vecihi"
labelFontName:#"Chalkduster"
labelFontSize:20
labelPositionX:CGRectGetMidX(self.frame)
labelPositionY:CGRectGetMidY(self.frame)]];
[self addChild:[self createImage:#"Spaceship"
imagePositionX:CGRectGetMidX(self.frame)
imagePositionY:CGRectGetMidY(self.frame)+60
imageWidth:40.0
imageHeight:60.0]];
[self.view addSubview:[self createButton:#"ButtonSample.png"
setTitleColor:[UIColor whiteColor]
buttonPositionX:(CGRectGetMidX(self.frame))
buttonPositionY:(CGRectGetMidY(self.frame)-200.0)
buttonWidth:200.0
buttonHeight:70.0]];
}
//Create any label with given parameters
-(SKLabelNode *) createLabel:(NSString *) labelTitle labelFontName:(NSString *) fontName labelFontSize:(int)fontSize labelPositionX:(CGFloat) x labelPositionY:(CGFloat) y {
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:fontName];
myLabel.text = labelTitle;
myLabel.fontSize = fontSize;
myLabel.position = CGPointMake(x,y);
return myLabel;
}
//Create any image with given parameters
-(SKSpriteNode *) createImage:(NSString *) imageName imagePositionX:(CGFloat) x imagePositionY:(CGFloat) y imageWidth:(CGFloat) width imageHeight:(CGFloat) height {
SKSpriteNode *myImage = [SKSpriteNode spriteNodeWithImageNamed:imageName];
myImage.position = CGPointMake(x, y);
myImage.size = CGSizeMake(width, height);
return myImage;
}
//Create any button wtih given parameters
-(UIButton *) createButton:(NSString *) buttonImage setTitleColor:(UIColor *) titleColor buttonPositionX:(CGFloat) x buttonPositionY:(CGFloat) y buttonWidth:(CGFloat) width buttonHeight:(CGFloat) height {
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
NSLog(#"%#", buttonImage);
myButton.frame = CGRectMake(x, y, width, height);
myButton.backgroundColor = [UIColor clearColor];
[myButton setTitleColor:titleColor forState:UIControlStateNormal];
[myButton setBackgroundImage:[UIImage imageNamed:buttonImage] forState:UIControlStateNormal];
return myButton;
}
The reason your code does not work, is because the scene's view property is set to nil, when initWithSize: is called.
You could handle this by moving the UI initialization code to the scene's didMoveToView: method. Then, in willMoveFromView:, you can perform cleanup (i.e. remove the UIButton from the view).
That way your scene-specific UI code stays in the scene, and doesn't clutter up the viewcontroller code. Especially, if you decide to add UI controls to multiple scenes - you only have one viewcontroller, and it could become messy fast.
You have to setup the button in the viewcontroller owning the scene, NOT the scene itself.
So, move
[self.view addSubview:[self createButton:#"ButtonSample.png"
setTitleColor:[UIColor whiteColor]
buttonPositionX:(CGRectGetMidX(self.frame))
buttonPositionY:(CGRectGetMidY(self.frame)-200.0)
buttonWidth:200.0
buttonHeight:70.0]];
in SampleScene.m
to SampleViewController.m like this
- (void) viewDidLoad {
[self.view addSubview:[self.scene createButton:#"ButtonSample.png"
setTitleColor:[UIColor whiteColor]
buttonPositionX:(CGRectGetMidX(self.view.frame))
buttonPositionY:(CGRectGetMidY(self.view.frame)-200.0)
buttonWidth:200.0
buttonHeight:70.0]];
//More commands not shown...
}
Please note the changes in the button positioning, and button creation
Replacing SamplesScene & SampleViewController to their corresponding names of course
I've got a UIBarButtonItem category where I build UIBarButtonItems with custom UIButtons, since I've found UIButtons easier to customize then UIBarButtonItems.
Now, I'd like to continue to use the BarButtonItem's target and action properties instead of using those in the button so that the BarButtonItem can continue to be customized externally without anyone having to know the implementation details (i.e., that it is using a button internally).
Now, in order to do that, I'm written up this code in my category:
+ (UIBarButtonItem *)backBarButtonItemWithColor:(UIColor *)color
{
UIImage *closeIcon = [MyImageUtility navBarBackArrow];
if (color) closeIcon = [closeIcon imageWithColorOverlay:color];
UIButton *close = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, closeIcon.size.width+10.0f, closeIcon.size.height+10.0f)];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:close];
[close setImage:closeIcon forState:UIControlStateNormal];
[close addTarget:item action:#selector(SD_executeBarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
return item;
}
- (void)SD_executeBarButtonItemAction
{
[self.target performSelector:self.action];
}
Whenever the SD_executeBarButtonItemAction is called, I get a exc_bad_access on the selector, though I am not sure why. Any ideas? Is there a way around this?
Thanks!
EDIT:
here is the code being called by that selector that is crashing:
void (^transition)(void) = ^(void) {
[self.rightContainer setFrame:[self offscreenContainerFrame]];
[self.centerContainer setAlpha:1.0f]; //TODO: this is unreliable in iOS6 -- we should add a view to the top of it to darken
[self.centerContainer setTransform:CGAffineTransformIdentity];
};
[self notifyWillShowPrimaryViewController];
[self performBlock:transition animated:YES completion:^(BOOL finished) {
[self notifyDidShowPrimaryViewController];
[self setForegroundController:self.primaryNavigationController];
if (block != NULL) block(finished);
}];
Your code is a recursive call.
- (void)SD_executeBarButtonItemAction
{
[self.target performSelector:self.action];
}
You set like:
[close addTarget:item action:#selector(SD_executeBarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
Where item is a UIBarButtonItem.
In my application, I added a cameraOverlayView to the camerapicker. It contains three subviews, two UIButtons and one UIImageView. The image is draggable. The buttons are used to change the image to another one.
When I touch a button, or the image, the tap-to-focus rect of the camera will always be displayed beneath (and the actual focus will change). Setting exclusiveTouch to YES on all three subviews doesn't change the behaviour.
Any idea, how i can stop the camerapicker from getting the touches of my buttons / subviews of my overlayview (but still getting the touches for touch-to-focus, switching cameras etc.)?
To give some more information. I'm using the following code, to allow camera interaction while having an overlayview, which subviews still get the touches.
// CameraOverlayView.m
[...]
// ignore touches on this view, but not on the subviews
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
id hitView = [super hitTest:point withEvent:event];
if (hitView == self) return nil;
else return hitView;
}
There are a couple of ways to stop this that I found. None of the ways do exactly what I would like it to do. But tweaking it around should give you something usable.
1.) Disable camera control. cameraPickerController.showsCameraControls = NO; This will ultimately take down the touch to focus from the screen but it would take down other controls too.
2.) Control the hitTest in your overlayView and set those area to nil. This solution is very awkward but it works for me.
This is my overlayView that manages a face mask
- (id)initWithFrame:(CGRect)frame imagePicker: (UIImagePickerController *) imagepicker{
self = [super initWithFrame:frame];
if (self) {
faceMaskButton = [UIButton buttonWithType:UIButtonTypeCustom];
faceMaskButton.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:.2];
faceMaskButton.frame = CGRectMake(10, 10, 70, 35);
faceMaskButton.layer.borderColor = [[UIColor colorWithRed:0 green:0 blue:0 alpha:.6] CGColor];
faceMaskButton.layer.borderWidth = 1;
faceMaskButton.layer.cornerRadius = 17;
[faceMaskButton setImage:[UIImage imageNamed:#"Facemask button"] forState:UIControlStateNormal];
[faceMaskButton addTarget:self action:#selector(facemask) forControlEvents:UIControlEventTouchDown];
faceMaskButton.exclusiveTouch = YES;
faceMaskButton.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
[self addSubview:faceMaskButton];
loadingFacemask = NO;
overlayImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Camera Face Overlay"]];
overlayImage.frame = CGRectMake((self.bounds.size.width - 400)/2, (self.bounds.size.height - 400)/3, 400, 400);
overlayImage.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
overlayImage.alpha = 0.9f;
[self addSubview:overlayImage];
imagePickerController = imagepicker;
}
return self;
}
- (void) facemask{
if (!loadingFacemask) {
loadingFacemask = YES;
[UIView animateWithDuration:.3 animations:^{
overlayImage.alpha = overlayImage.alpha? 0:1;
faceMaskButton.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:1];
} completion:^(BOOL finished) {
faceMaskButton.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:.2];
loadingFacemask = NO;
}];
}
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if ((self.bounds.size.height - 44)<point.y){
return imagePickerController.view;
}
if (point.x < faceMaskButton.bounds.origin.x + faceMaskButton.bounds.size.width +10 && point.y < faceMaskButton.bounds.origin.y + faceMaskButton.bounds.size.height + 10) {
[self facemask];
return nil;
}
return self;
}
So if you get tweaking, this will work for you. I am still looking for the magic wand that make this work without all the hacks. No sign of it still :(