Highlight buttons on screen - ios

im trying to stand out buttons in my screen, i want to change their background image, wait few seconds, restore background image and same with next button.
I´ve wrote this code:
-(void)animateButtons
{
UILabel * lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, scroll.frame.origin.y-20, [UIScreen mainScreen].bounds.size.width, 20)];
[lbl setTextAlignment:NSTextAlignmentCenter];
for(int c=0;c<arr.count&&animationRunning;c++)
{
MenuItem * m = [arr objectAtIndex:c];
[lbl setText:m.name];
MyButton * b = (MyButton*)[self.view viewWithTag:c+1];
NSMutableString * str = [[NSMutableString alloc]initWithString:m.image];
[str appendString:#"_focused.png"];
[b setBackgroundImage:[UIImage imageNamed:str] forState:UIControlStateNormal];
sleep(2.5);
str = [[NSMutableString alloc]initWithString:m.image];
[str appendString:#"_normal.png"];
[b setBackgroundImage:[UIImage imageNamed:str] forState:UIControlStateNormal];
if(c==arr.count-1)
{
animationRunning=false;
}
}
}
that method its called in this way, so it doesnt block UI thread.
[NSThread detachNewThreadSelector:#selector(animateButtons) toTarget:self withObject:nil];
But it just change first button background and then nothing.
Using NSLog i can see that the method is still running but no changes on Buttons.
How can i achieve this?
Thanks and sorry for my bad english.

You cannot change UI properties from a background thread, this leads to all kinds of problems including crashes. To keep your original algorithm you could dispatch the UI update back to the main thread. But this is not a very efficient use of threads, just use a simple NSTimer that runs on the main thread instead.

As already mentioned, perform all of your UI changes on the main thread. Here's an option for accomplishing what you're trying to do, no NSTimer required.
-(void)animateButtons
{
for (...)
{
// set focused state
}
[self performSelector:#selector(restoreButtons) withObject:nil afterDelay:2.5];
}
-(void)restoreButtons
{
for (...)
{
// set normal state
}
}

Related

Why does the UIButton never hide, and then come back unhidden?

So self.pic is the UIButton that I want to hide. But it never actually hides it for some reason.
Please Help
- (void)tick:(NSTimer*)time
{
self.pic.hidden = YES;
[NSThread sleepForTimeInterval:1];
self.pic.hidden = NO;
[self.pic setEnabled:NO];
if (self.checkPic == YES)
{
self.lives--;
self.livesLabel.text = [NSString stringWithFormat:#"Lives : %d", self.lives];
}
[self.pic setImage:[self backgroundImageForGame] forState:UIControlStateNormal];
[self check];
self.pic.enabled = YES;
}
Just to elaborate on what #rmaddy said in his comment, you shouldn't ever sleep the main (or UI) thread. When you do this, you're blocking the thread that is responsible for making the visual changes to the button, like whether or not it is hidden. The solution is to do the waiting asynchronously, and GCD has a built in function that makes this painless.
dispatch_after() allows you to create a block that will be executed after a specified delay, on a queue of your choosing. Since you want to update the UI, you'll want to come back to the main queue to make the changes to the on screen button. Here's an example:
self.pic.hidden = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.pic.hidden = NO;
});

How to glow a UIButton randomly from a sequence of buttons continuously

I am working on a memory based matching puzzle game. For that I need to glow buttons randomly from sequence. I have been googling sample puzzles,games & code snippets since a week or so, yet I was unable to trace out an appropriate sample project or some source to get started.
I am glowing the lights(buttons) by changing its background images(imitation), I have already set the default images for buttons(coloured bulb when in stable state) in story board. I have used NSMutableArray of IBOutletCollection i.e. tapLightButtons. Here is what I tried to get the sequence of lights glowing in a random mode to get the game experience.
-(void)startGlowingLights
{
[self shuffleValuesInArray:_tapLightButtons];
[self shuffleValuesInArray:lightArray];
[self shuffleValuesInArray:_initialButtonImages];
[self performSelector:#selector(animateButtonSequence) withObject:self afterDelay:0.2];
}
- (void) animateButtonSequence
{
__block UIButton *tapLight;
[UIView animateWithDuration:0.15
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
// button flash animation
} completion:^(BOOL finished) {
if (finished) {
if (i < _tapLightButtons.count) {
i++;
[self performSelector:#selector(animateButtonSequence) withObject:self afterDelay:1.0];
tapLight = [self.tapLightButtons objectAtIndex:i-1];
UIImage *glownLight = [UIImage imageNamed:[lightArray objectAtIndex:i-1]];
[tapLight setBackgroundImage:glownLight forState:UIControlStateNormal];
[[TTLMusicPlayer sharedInstance] playGlowLightSound:[[NSBundle mainBundle] pathForResource:#"beep" ofType: #"mp3"]];
//Reset the completed glowed button to previous state(off mode)
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (i != 0 && i < _initialButtonImages.count) {
[tapLight setBackgroundImage:[self.initialButtonImages objectAtIndex:i-1] forState:UIControlStateNormal];
}
});
}
else {
i = 0;
}
NSLog(#"i value is %d",i);
}
}];
}
-(NSMutableArray *)shuffleValuesInArray:(NSMutableArray *)shuffledArray
{
for (NSInteger x=0;x<[shuffledArray count];x++)
{
NSInteger randomNumber = (arc4random() % ([shuffledArray count] - x)) + x;
[shuffledArray exchangeObjectAtIndex:x withObjectAtIndex:randomNumber];
}
return shuffledArray;
}
tapLightButtons is the array which holds puzzled buttons(lights) which glows automatically one after another in random fashion.
lightArray is an array holding all the appropriate colour images(flash to produce glowed affect)
initialButtonImages contains array of all default(initial) images of buttons(off mode lights)
Some how I could partially achieve what I wanted to, surprisingly even after shuffling all arrays, still I see variations in the order because the glowed image should be the corresponding of stable(off mode coloured light) and then after resetting, the stable image should match the one before the light was actually glowed.
Posted the question on game dev site from Stack Exchange as well!!
Game Screen for better understanding
After flashing of coloured light
How to properly glow the buttons randomly from sequence?
I would do this a different way that leads to simpler code. I created an array of 9 custom buttons in IB and added them to an outlet collection. I subclassed the buttons, and added a flashWithOnTime: method, so the button itself would take care of switching between the normal and highlighted states.
This is the method in the button subclass,
-(void)flashWithOnTime:(CGFloat)onTime {
[self setHighlighted:YES];
[self performSelector:#selector(setHighlighted:) withObject:#NO afterDelay:onTime];
}
This is the code in the view controller that starts the sequence (from a button tap). Rather than shuffling, I pick a random index out of an array, and then delete that entry so it can't be picked again. The methods offImageWithSolidColor and onImageWithSolidColor, are just methods I used to create the images.
#interface ViewController ()
#property (strong, nonatomic) IBOutletCollection(RDFlashingButton) NSArray *buttons;
#property (strong,nonatomic) NSArray *indexes;
#property (strong,nonatomic) NSMutableArray *mutableIndexes;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.indexes = #[#0,#1,#2,#3,#4,#5,#6,#7,#8];
for (RDFlashingButton *aButton in self.buttons) {
[aButton setBackgroundImage:[self offImageWithSolidColor] forState:UIControlStateNormal];;
[aButton setBackgroundImage:[self onImageWithSolidColor] forState:UIControlStateHighlighted];
}
}
-(void)flashRandomly {
if (self.mutableIndexes.count > 0) {
int index = arc4random_uniform(self.mutableIndexes.count);
int value = [self.mutableIndexes[index] intValue];
RDFlashingButton *aButton = self.buttons[value];
[aButton flashWithOnTime:0.4];
[self.mutableIndexes removeObjectAtIndex:index];
[self performSelector:#selector(flashRandomly) withObject:nil afterDelay:.4];
}
}
-(IBAction)startFlashing:(id)sender {
self.mutableIndexes = [self.indexes mutableCopy];
[self flashRandomly];
}

check which UIButton was tapped

I have created a several buttons like this:
[self makeButtonsWithX:0.0f y:180.0f width:640.0f height:80.0f color:[UIColor colorWithRed:0.796 green:0.282 blue:0.196 alpha:1] button:self.redButton];
[self makeButtonsWithX:0.0f y:260.0f width:640.0f height:80.0f color:[UIColor colorWithRed:0.761 green:0.631 blue:0.184 alpha:1] button:self.yellowButton];
using this function:
- (void)makeButtonsWithX:(CGFloat)x y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height color:(UIColor *)color button:(UIButton *)button
{
button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(x, y, width, height);
button.backgroundColor = color;
[self.view addSubview:button];
[button addTarget:self action:#selector(tappedButton:) forControlEvents:UIControlEventTouchUpInside];
}
When I tap one of them I want to know which one was tapped, using this function:
- (void)tappedButton:(UIButton *)button
{
//NSLog(#"tapped");
if ([button isEqual:self.redButton]) {
NSLog(#"Red");
}
}
Nothing happens. If I uncomment the first NSLog in the last function however, it prints every time I press a button (doesn't matter which one). Why isn't my if-statement working?
Cheers.
When you're creating the buttons, give them unique tags.
Change your tappedButton: method to this:
- (void)tappedButton:(id)sender {
if([sender tag] == 1) {
NSLog(#"Red");
}
}
And I'd probably create an enum for the tags:
typedef NS_ENUM(NSUInteger, ButtonTags) {
ButtonUnknown = 100,
ButtonRedTag = 101,
ButtonBlueTag = 102,
ButtonGreenTag = 103,
ButtonYellowTag = 104,
ButtonWhiteTag = 105
};
Now use the enum both when setting and checking the tags.
if([sender tag] == ButtonRedTag)
To set a tag:
[button setTag:ButtonRedTag];
There may be another way of determine which button was pressed, but as far as I'm concerned, using [sender tag]; is the best method. Consider this... suppose you want to call a method either when a button is pressed or a text field has resigned first responder? For example, imagine a login page, with a username/password text field and a button for login and create new account. Using sender tag, you can EASILY make all your UI elements at least start in the same method:
- (void)handleUserInteraction:(id)sender {
switch([sender tag]) {
case LoginButtonTag:
// do stuff
break;
case PasswordTextFieldTag:
// do stuff
break;
case NewAccountButtonTag:
// do stuff
break;
case BackgroundViewTappedTag:
// do stuff
break;
}
}
It might make more sense to just hook all of these UI elements up to different methods in the first place, and certainly, within the switch, they should call out to different methods, but suppose there's some bit of logic you want to execute in all 4 of these cases? Put it just before or just after the switch instead of putting it in all 4 of the methods you've created for handling these different cases, etc.
In addition, you cannot pass the button "to make" by reference. The way you call makeButtonsWithX will to not change self.redButton.
self.redButton in the if statement will be nil.
Change the return type of make ButtonsWithX from (void)to (UIButton *) and do it like this:
self.redButton = [self makeButtonsWithX: ...

Async UIView content not showing up when coming back to main thread

When loading my UIViewController, I basically put a spinner in the middle of the page until the content loads, then come back on the main thread to add the subviews :
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *aiv_loading = ... // etc
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
// Display the content
dispatch_sync(dispatch_get_main_queue(), ^{
if (s_checkout && v_invoice_content) {
[aiv_loading removeFromSuperview];
[self showContentWithText:s_checkout AndInvoice:v_invoice_content];
} else {
NSLog(#"No data received!"); // is thankfully not called
}
});
});
}
- (void) showContentWithText:(NSString *)s_checkout AndInvoice:(UIView *)v_invoice {
[self.view addSubview:[self checkoutTextWithText:s_checkout]]; // Most of the time displayed text
[self.view addSubview:[self completeCheckout]]; // always Displayed UIButton
[self.view addSubview:[self divider]]; // always displayed UIImageView
// Summary title
UILabel *l_summary = [[UILabel alloc] initWithFrame:CGRectMake(0, [self divider].frame.origin.y + 6 + 10, self.view.bounds.size.width, 20)];
l_summary.text = NSLocalizedString(#"Summary", nil);
[self.view addSubview:l_summary];
CGRect totalRect = CGRectMake([self divider].frame.origin.x, [self divider].frame.origin.y + 6 + 30, self.view.bounds.size.width - [self divider].frame.origin.x, 90);
_v_invoice = v_invoice;
_v_invoice.frame = totalRect;
[self.view addSubview:[self v_invoiceWithData:v_invoice]]; // THIS Pretty much never displayed
UITextView *l_invoice = [[UITextView alloc] initWithFrame:CGRectMake(0, _v_invoice.frame.origin.y + _v_invoice.frame.size.height + offset, 320.0, 50)];
l_invoice.text = NSLocalizedString(#"summary_emailed", nil);
[self.view addSubview:l_invoice]; // Always displayed
}
However, not all the content is displayed. The invoice is never there at first, but gets displayed after a couple of minutes. The other async-created string, s_content is sometimes not displayed.
This seems to be random with the content creation. The end result is pretty neat, but not reliable for a production version.
I used the undocumented [self.view recursiveDescription] to check if everything was there, and even if I don't see it, they are all there with what seems to be correct frames.
Any pointers?
- layoutSubviews did not help!
- putting a background color to the invoice view is showing the background color
I suspect this line is your problem:
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
As you are calling this in a background dispatch queue. Any work involving UIKit should be done on the main queue/thread. Either move that into the main thread block, or if building the view is dependent on data from a network call, change your invoiceViewForBooking method to return the data first, and build your view in the main thread with that data.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
id someData = [[BRNetwork sharedNetwork] invoiceDataForBooking:locBooking.objectId];
// Display the content
dispatch_async(dispatch_get_main_queue(), ^{
UIView *v_invoice_content = [invoiceViewWithData:someData];
});
});
I'd also suggest using dispatch_async instead of dispatch_sync on the main queue.
Solved! I ended up removing the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
To only leave the async block on the main queue:
// Display the content from a new async block on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[aiv_loading removeFromSuperview];
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
[self showContentWithText:s_checkout AndInvoice:[[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId]];
});
which did the trick, viewcontroller's view can appear without waiting for the view to load the remote data!
You should put your code in viewDidAppear instead of viewDidLoad. Whatever is related to display (even launching async blocks) should always be in viewWillAppear or viewDidAppear.
Also, I recommend you to use dispatch_async(dispatch_get_main_queue(), ^{}) instead of your dispatch_sync since you still want your block to be performed async (but on main thread).
Dont forget to call super methods in your viewDidAppear.

How to send method to view from action called by UIButton

I understand (I think) why this isn't working, but I don't know how to get it to work.
I have a method I call in viewWillAppear like so (I've simplified the code for the sake of this question):
- (void)viewWillAppear:(BOOL)animated
{
[self displayHour:0];
}
I then have a little for loop that builds some buttons in viewDidLoad:
NSUInteger b = 0;
for (UIButton *hourButton in hourButtons) {
[hourButton setTag:b];
[hourButton setTitle:[NSString stringWithFormat:#"%lu", (unsigned long)b] forState:UIControlStateNormal];
[hourButton addTarget:self action:#selector(showHour:) forControlEvents:UIControlEventTouchUpInside];
b++;
}
Then for the action I have this:
- (IBAction)showHour:(id)sender
{
// Logs 0, 1, etc. depending on which button is tapped
NSLog(#"Button tag is: %lu", (long int)[sender tag]);
// This currently throws this error:
// No visible #interface for 'UIView' declares the selector 'displayHour:'
// What I want to do is be able to call this method on
// the main view and pass along the button tag of the
// button that was tapped.
[mainView displayHour:(long int)[sender tag]];
}
THE QUESTION is how do I call displayHour to the main view from the showHour: action?
If you need it, the displayHour method looks like this (simplified, basically updates some labels and image views on the main view):
- (void)displayHour:(NSUInteger)i
{
// The temperature
hourTemp = [[hourly objectAtIndex:i] valueForKeyPath:#"temperature"];
// The icon
hourIcon = [[hourly objectAtIndex:i] valueForKeyPath:#"icon"];
// The precipitation
hourPrecip = [[hourly objectAtIndex:i] valueForKeyPath:#"precipProbability"];
// SET DISPLAY
[hourTempField setText:hourTemp];
[hourIconFrame setImage:hourIcon];
[hourPrecipField setText:hourPrecip];
}
Thanks!
Instead of
[mainView displayHour:(long int)[sender tag]];
do:
[self displayHour:(long int)[sender tag]];

Resources