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

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];
}

Related

Recording A Users Interaction With Interface

I have a lot of UIButtons in my app. I need to record which ones the user pushed, exactly the time elapsed between each button press, and then relay the information back to the user. Now, I've tried figuring out the logic to do this for quite some time. This is what I've tried first:
I start by firing an NSTimer. Every time the user presses a button, I store the time he pushed the button in arrayOne, and store the button he pushed in arrayTwo.
I have a "Playback" button. The goal of this playback button is to loop through both arrays, programatically pushing the buttons the user pressed at the elapsed time in which they pressed them.
I can probably achieve my goal this way, but this is just messy code and I don't like the design of it at all. What I'm looking for is an easier path. Is there an API that will record and playback the interaction?
I've found various links such as:
Audio Recording and Playback
That achieve similar to what I'm trying to do, but the sounds I'm loading into the app that play when each button is tapped are not recorded via microphone. They are included in the bundle.
What I did for the solution was make every button have a unique tag and call the same method "onAppButtonPressed" using tags to execute specific code for each button. Before anything though I created a fake button execution so I could mark when the app was opened. After executing button specific code when a UIButton was pressed, I stored information in an array with the button's tag and the button's fire date. When I call the startPlayback method, a for loop loops through all of the button data and schedules each button with the correct timing since the view loaded. I made a simple storyboard with three test buttons and a start playback button.
View Controller:
#import "PlaybackButtonEventViewController.h"
#import "ButtonPlaybackData.h"
#interface PlaybackButtonEventViewController ()
#end
#implementation PlaybackButtonEventViewController
{
NSMutableArray *playbackInformationArray;
BOOL playbackMode;
}
#define kInitialPlaybackData -1
#define kButtonOneId 1
#define kButtonTwoId 2
#define kButtonThreeId 3
#define kPlaybackButtonId 4
- (void) viewDidLoad
{
[super viewDidLoad];
self->playbackInformationArray = [NSMutableArray array];
self->playbackMode = false;
ButtonPlaybackData *initialPlaybackData = [[ButtonPlaybackData alloc] init];
initialPlaybackData.buttonTag = kInitialPlaybackData;
initialPlaybackData.buttonFireDate = (NSDate*) [NSDate date];
[self->playbackInformationArray addObject:initialPlaybackData];
}
- (void) startPlaybackMode
{
self->playbackMode = true;
for (int playbackIndex = 0; playbackIndex < self->playbackInformationArray.count; playbackIndex++)
{
ButtonPlaybackData *currentPlaybackData = [self->playbackInformationArray objectAtIndex:playbackIndex];
if (currentPlaybackData.buttonTag == kInitialPlaybackData)
continue;
ButtonPlaybackData *initialPlaybackData = [self->playbackInformationArray objectAtIndex:0];
long timeToWait = (currentPlaybackData.buttonFireDate.timeIntervalSince1970 - initialPlaybackData.buttonFireDate.timeIntervalSince1970);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeToWait * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self onAppButtonPressed:[self.view viewWithTag:currentPlaybackData.buttonTag]];
if ((playbackIndex + 1) == self->playbackInformationArray.count)
{
self->playbackMode = false;
NSLog(#"Playback has ended!");
}
});
}
}
- (IBAction) onAppButtonPressed: (id) sender
{
if (![sender isKindOfClass:[UIButton class]])
return;
UIButton *button = (UIButton*) sender;
switch (button.tag)
{
case kButtonOneId:
NSLog(#"Button one pressed!");
break;
case kButtonTwoId:
NSLog(#"Button two pressed!");
break;
case kButtonThreeId:
NSLog(#"Button three pressed!");
break;
case kPlaybackButtonId:
[self startPlaybackMode];
break;
}
if (!playbackMode)
{
ButtonPlaybackData *buttonPlaybackData = [[ButtonPlaybackData alloc] init];
buttonPlaybackData.buttonTag = button.tag;
buttonPlaybackData.buttonFireDate = (NSDate*) [NSDate date];
[self->playbackInformationArray addObject:buttonPlaybackData];
}
}
#end
Now for the ButtonPlaybackData object, it has the following header file with an empty m file:
#import <Foundation/Foundation.h>
#interface ButtonPlaybackData : NSObject
#property(nonatomic) NSInteger buttonTag;
#property(nonatomic, retain) NSDate *buttonFireDate;
#end
Hope this helps!

Prolong UIPickerView [selectRow: inComponent: animated:]'s animation

I'm using a UIPickerView to display random numbers. The user can press a button, and thus trigger a random selection of a number inside the UIPickerView.
It does not matter how many objects or numbers are being displayed inside the UIPickerView, when I call the method:
[self.picker selectRow:randomRow inComponent:0 animated:YES];
It always gets animated with the same time interval.
Is there any option or method to prolong the animation time interval of above method?
I have tried placing it in an animation block:
[UIView beginAnimations:#"identifier" context:nil];
// code
[UIView commitAnimations];
but this seems to be a dead end.
I have also tried executing it in a completion blocks:
// 0.34f is the approximate defualt apple animation
[UIView animateWithDuration:0.34f animations:^{
[self.picker selectRow:randomRow inComponent:0 animated:YES];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.34f animations:^{
[self.picker selectRow:randomRow inComponent:0 animated:YES];
} completion:nil];
}];
Any help would be appreciated.
I made a project and play for a while till I find out this tricky solution. It base on the method performSelector:afterDelay
Here is the touch up inside code of your button:
- (void)click:(id)sender
{
int randomRow = <>;//Your random method
int currentRow = [_picker selectedRowInComponent:0];
int i = 0;
while(1)
{
i++;
NSString *rowToSelectString = [NSString stringWithFormat:#"%d", currentRow];
NSDictionary *rowToSelectDictionary = #{#"row":rowToSelectString};
if(randomRow < currentRow)
{
// Go backward
currentRow--;
}
else
{
// Go forward
currentRow++;
}
[self performSelector:#selector(selectRowInPicker:) withObject:rowToSelectDictionary afterDelay:i*0.1];//Change the delay as you want
if(currentRow == randomRow)
{
break;
}
}
}
And the trick:
-(void)selectRowInPicker:(NSDictionary *)rowToSelectDictionary
{
NSInteger row = [[rowToSelectDictionary objectForKey:#"row"] integerValue];
[_picker selectRow:row inComponent:0 animated:YES];
}
This work well for me. Tell me if you're having problem with it.
Finally, I have implemented a "thinner" version of the chosen answer:
- (IBAction)spinTheWheelButtonPressed:(UIButton *)sender
{
for (int i = 0; i < 30; i++) {
NSUInteger randomRow = arc4random_uniform((int)[self.dataSource count]);
[self performSelector:#selector(selectRowInPicker:) withObject:#(randomRow) afterDelay:i*0.1];
}
}
- (void)selectRowInPicker:(NSNumber *)randomRow
{
[self.picker selectRow:[randomRow integerValue] inComponent:0 animated:YES];
}
I had a similar problem , but unfortunately there is no easy way to delay the animation thats as far as I know.
So I went around and placed a UIView on top of the pickerView. I subclassed that view to pass all of its touches to the pickerView and when something was selected I just drew a layer with less opacity in the view where the row would usually come to rest and animated it in a animation block since the selection is always right at the middle of the pickerView.

Passing an animating UIImageView between viewControllers in prepareForSegue:?

Based on some earlier questions (ex: ios5 how to pass prepareForSegue: a UIImageView) I was wondering how smooth this technique is, say when doing a modal segue with animated = NO.
I can toss together a test but I imagine someone has already scoped out the performance and practical pitfalls of using this as a transition technique. I would be interested in hearing any details - for example, does the UIImageView animation restart or does it continue apace?
If you pass an animating image view to another controller, it looks like the animation continues apace. I can't see any "hitch" in the animation. Going forward with a modal presentation worked fine. I couldn't go back to the first controller, by just dismissing, I had to add the image view back to the view to make that work, and even then it only worked if I turned off auto layout (otherwise I get an error saying it can't install a constraint). So, to go back and forth with modals (with no animation) I had this in the first controller:
- (void)viewDidLoad {
[super viewDidLoad];
self.isFirst = YES;
NSMutableArray *imgArray = [NSMutableArray array];
for (int i = 1; i< 41; i++) {
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:#"New_PICT%04d.jpg",i]];
if (img) {
[imgArray addObject:img];
}
}
self.iv.animationImages = imgArray;
self.iv.animationDuration = 10;
[self.iv startAnimating];
}
-(void)viewDidAppear:(BOOL)animated {
if (self.isFirst) {
self.isFirst = NO;
}else{
self.iv.frame = CGRectMake(48, 48, 48, 48);
[self.view addSubview:self.iv];
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SecondViewController *sec = segue.destinationViewController;
sec.iv = self.iv;
}

How to animate transition from one state to another for UIControl/UIButton? [duplicate]

This question already has an answer here:
Fading out an UIButton when touched
(1 answer)
Closed 9 years ago.
I have a UIButton that changes image on highlight. When transitioning from UIControlStateHighlight to UIControlStateNormal, I want the highlighted image to slowly fade back into the normal image. Is there an easy way to do this?
I ended up subclassing UIButton. Here's the implementation file code. I took some app-specific stuff out, so I haven't tested this exact code, but it should be fine:
#import "SlowFadeButton.h"
#interface SlowFadeButton ()
#property(strong, nonatomic)UIImageView *glowOverlayImgView; // Used to overlay glowing animal image and fade out
#end
#implementation SlowFadeButton
-(id)initWithFrame:(CGRect)theFrame mainImg:(UIImage*)theMainImg highlightImg:(UIImage*)theHighlightImg
{
if((self = [SlowFadeButton buttonWithType:UIButtonTypeCustom])) {
self.frame = theFrame;
if(!theMainImg) {
NSLog(#"Problem loading the main image\n");
}
else if(!theHighlightImg) {
NSLog(#"Problem loading the highlight image\n");
}
[self setImage:theMainImg forState:UIControlStateNormal];
self.glowOverlayImgView = [[UIImageView alloc] initWithImage:theHighlightImg];
self.glowOverlayImgView.frame = self.imageView.frame;
self.glowOverlayImgView.bounds = self.imageView.bounds;
self.adjustsImageWhenHighlighted = NO;
}
return self;
}
-(void)setHighlighted:(BOOL)highlighted
{
// Check if button is going from not highlighted to highlighted
if(![self isHighlighted] && highlighted) {
self.glowOverlayImgView.alpha = 1;
[self addSubview:self.glowOverlayImgView];
}
// Check if button is going from highlighted to not highlighted
else if([self isHighlighted] && !highlighted) {
[UIView animateWithDuration:1.0f
animations:^{
self.glowOverlayImgView.alpha = 0;
}
completion:NULL];
}
[super setHighlighted:highlighted];
}
-(void)setGlowOverlayImgView:(UIImageView *)glowOverlayImgView
{
if(glowOverlayImgView != _glowOverlayImgView) {
_glowOverlayImgView = glowOverlayImgView;
}
self.glowOverlayImgView.alpha = 0;
}
#end
You could also just pull the highlighted image from [self imageForState:UIControlStateHighlighted] and use that, it should work the same. The main things are to make sure adjustsImageWhenHighlighted = NO, and then overriding the setHighlighted: method.

UIProgressView not updating?

I have started playing with UIProgressView in iOS5, but havent really had luck with it. I am having trouble updating view. I have set of sequential actions, after each i update progress. Problem is, progress view is not updated little by little but only after all have finished. It goes something like this:
float cnt = 0.2;
for (Photo *photo in [Photo photos]) {
[photos addObject:[photo createJSON]];
[progressBar setProgress:cnt animated:YES];
cnt += 0.2;
}
Browsing stack overflow, i have found posts like these - setProgress is no longer updating UIProgressView since iOS 5, implying in order for this to work, i need to run a separate thread.
I would like to clarify this, do i really need separate thread for UIProgressView to work properly?
Yes the entire purpose of progress view is for threading.
If you're running that loop on the main thread you're blocking the UI. If you block the UI then users can interact and the UI can't update. YOu should do all heavy lifting on the background thread and update the UI on the main Thread.
Heres a little sample
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:#selector(backgroundProcess) withObject:nil];
}
- (void)backgroundProcess
{
for (int i = 0; i < 500; i++) {
// Do Work...
// Update UI
[self performSelectorOnMainThread:#selector(setLoaderProgress:) withObject:[NSNumber numberWithFloat:i/500.0] waitUntilDone:NO];
}
}
- (void)setLoaderProgress:(NSNumber *)number
{
[progressView setProgress:number.floatValue animated:YES];
}
Define UIProgressView in .h file:
IBOutlet UIProgressView *progress1;
In .m file:
test=1.0;
progress1.progress = 0.0;
[self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
- (void)makeMyProgressBarMoving {
NSLog(#"test %f",test);
float actual = [progress1 progress];
NSLog(#"actual %f",actual);
if (progress1.progress >1.0){
progress1.progress = 0.0;
test=0.0;
}
NSLog(#"progress1.progress %f",progress1.progress);
lbl4.text=[NSString stringWithFormat:#" %i %%",(int)((progress1.progress) * 100) ] ;
progress1.progress = test/100.00;//actual + ((float)recievedData/(float)xpectedTotalSize);
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
test++;
}
I had similar problem. UIProgressView was not updating although I did setProgress and even tried setNeedsDisplay, etc.
float progress = (float)currentPlaybackTime / (float)totalPlaybackTime;
[self.progressView setProgress:progress animated:YES];
I had (int) before progress in setProgress part. If you call setProgress with int, it will not update like UISlider. One should call it with float value only from 0 to 1.

Resources