I'm working on a project for school that includes a set of buttons that create NStimers used as a countdown timer. Ideally, the time would display as the title of the UIButton that started the timer.
So far I have only been able make this work if when setting the display of the time I refer to a specific instance of a UIButton rather than a somehow passing a UIButton argument.
I also have declared "time" NSString that holds the current displayed value calculated by the NSTimer. This is not how the final implementation should be, and I only have it set up like this just to have it partially working.
Code for one of the buttons:
b1 = [[UIButton alloc]
initWithFrame:CGRectMake(neutral.frame.size.width * 0.56, neutral.frame.size.height * 0.18, neutral.frame.size.height * 0.64, neutral.frame.size.height * 0.64)];
[b1.titleLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:35.0]];
[b1 setBackgroundImage: forState:UIControlStateNormal];
Method called from button press action:
-(void)buttonClicked:(id)sender
{
//do stuff
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:[NSNumber numberWithInt:t] repeats:YES]; }
NSTimer timer fired method:
-(void)timerFired
{
if((curMin>0 || curSec>=0) && curMin>=0)
{
if(curSec==0)
{
curMin-=1;
curSec=59;
}
else if(curSec>0)
{
curSec-=1;
}
if(curMin>-1)
{
time = [NSString stringWithFormat: #"%d%#%02d",curMin,#":",curSec];
[b1 setTitle:time forState:UIControlStateNormal];
}
}
else
{
[b1 setTitle: nil forState:UIControlStateNormal];
[timer invalidate];
}
}
I hope that was clear enough as to what I am trying to accomplish. I am somewhat surprised that I haven't been able to find anything to walk me through this. I am flexible to changing how exactly this works. All that really matters is that each button can start a unique countdown timer.
I'm sure that this code is sloppy, but Objective C is relatively new to me so any help at all would be appreciated.
Don't store time as minutes/seconds, just use seconds, as all that greater than 60 logic is really tedious isn't it.
I've chosen to call the instance variable _secondsLeft so I will know what it means when I look at the code in 6 months time:
-(void)timerFired
{
NSString *formatted = #"";
if (secondsLeft > 0)
{
secondsLeft--;
formatted = [NSString stringWithFormat:#"%d:%02d", (secondsLeft / 60), (secondsLeft % 60)];
} else {
[timer invalidate];
}
[b1 setTitle:formatted forState:UIControlStateNormal];
}
Related
My general question is how do you have an object with an image array:
#import <Foundation/Foundation.h>
#interface Object: NSObject {
UIImage *image[imageNumber];
}
And then take these images and apply them to a button in a different class, starting with imageArray[0] and every few seconds changing it to imageArray[1], etc, but stopping the loop at the last image.
What I specifically did was create a Plant object:
#import <Foundation/Foundation.h>
#interface Plant : NSObject {
UIImage *plantImage[3];
int imageNumber; //to specify which image to display, increments with timer
NSTimer *plantImageTimer;
}
and declared then made a few methods like
-(void)addPlantImages:(UIImage *) firstImage withPlantImage2:(UIImage *) secondImage withPlantImage3:(UIImage *) thirdImage{
plantImage[0] = firstImage;
plantImage[1] = secondImage;
plantImage[2] = thirdImage;
}
//this makes the Plant into a button
-(void)createPlantButtonForPlant:(Plant *) plantType forView:(UIView *) view withButtonRect:(CGRect) buttonRect{
imageNumber=0;
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = buttonRect;
button.hidden = NO;
[view addSubview:button];
[plantType growPlant:button]; //explained in code below
}
And from here I don't know what to do anymore.
I tried using an NSTimer, but you can't put parameters into the selector, so I can't specify which button (or Plant) I want to cycle images through.
Extra tidbits of code in Plant that I have (which do not work); I'm using NSTimer userinfo incorrectly, but I don't understand how to use it, to be honest.
-(void)growPlant:(UIButton *) button{
plantImageTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(changePlantImage:) userInfo:button repeats:YES];
}
-(void)changePlantImage: (UIButton *) plantButton{
if(imageNumber == 3){
[plantImageTimer invalidate];
} else {
[plantButton setImage:plantImage[imageNumber] forState:UIControlStateNormal];
imageNumber++;
}
}
I imported Plant into my view controller (called PracticeViewController) and tried to make it so that when I clicked a button, a Plant button popped up and cycled through the images.
(void)buttonAction{
theButton.hidden = YES; //I did make this button, just didn't add the code here. Declared in header.
CGRect imageViewRect = CGRectMake(100, 100, 100, 100);
Plant *testPlant = [Plant new];
[testPlant addPlantImages:[UIImage imageNamed:#"plant2.png"] withPlantImage2:[UIImage imageNamed:#"plant2.png"] withPlantImage3:[UIImage imageNamed:#"plant2.png"];
[testPlant createButtonForPlant:testPlant forView:self.view withButtonRect:imageViewRect];
}
If you need more code or other information, just comment below and I will reply asap.
Now that I've done more programming, I realize how complicated I made this code lol. Anyway, all I would have had to do was add
[button addTarget:self action:#selector(changePlantImage) forControlEvents:UIControlEventTouchUpInside];
after making the button instance. This would then scroll through whichever images the plant had in its array.
I am trying to code a controller for an image view to move using buttons.
inside up button code :
-(IBAction)upButton:(id)sender{
mainChac.center = CGPointMake(mainChac.center.x, mainChac.center.y - 5);
}
code is working properly but I have to tap it repeatedly to keep moving up, I wanna know which method to call, to move it, while holding the up button.
edit :
-(void)touchDownRepeat{
mainChac.center = CGPointMake(mainChac.center.x, mainChac.center.y - 5);
}
-(IBAction)upButton:(id)sender{
[upButton addTarget:self action:#selector(touchDownRepeat) forControlEvents:UIControlEventTouchDownRepeat];
}
edit 2: solution
-(void)moveUp{
mainChac.center = CGPointMake(mainChac.center.x, mainChac.center.y - 5);
}
- (void)holdDown
{
NSLog(#"hold Down");
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(moveUp) userInfo:nil repeats:YES];
}
- (void)holdRelease
{
NSLog(#"hold release");
[timer invalidate];
}
-(IBAction)upButton:(id)sender{
[upButton addTarget:self action:#selector(holdDown) forControlEvents:UIControlEventTouchDown];
[upButton addTarget:self action:#selector(holdRelease) forControlEvents:UIControlEventTouchUpInside];
}
EDIT:
What you need to to have 2 events, one for while the button is pressed down and another for when the button is released like so:
[aButton addTarget:self action:#selector(holdDown) forControlEvents:UIControlEventTouchDown];
[aButton addTarget:self action:#selector(holdRelease) forControlEvents:UIControlEventTouchUpInside];
- (void)holdDown
{
NSLog(#"hold Down");
}
- (void)holdRelease
{
NSLog(#"hold release");
}
Have your hold down function start a loop, and your holdRelease stop the loop.
EDIT-2:
An easy way (but perhaps not the best way) to achieve this loop would be to use NSTImer scheduledTimerWithTimeInterval inside the hold down and invalidate it in the release method.
all of this stuff has been done before, please try googling this stuff. Here is a stackoverlfow question with an example: ios scheduledTimerWithTimeInterval for amount of time
So I have two problems but both are related.
I created a "animation" that moves a progress view when a button is clicked by using NSTimer. This animation works fine the first couple times the button is clicked but after that it starts speeding up the process, practically skipping from the initial start of the NSTimer loop to the end of the NSTimer loop. I was wondering if anyone has ever heard of this issue and/or knows a solution?
I created the same thing in question 1 but in a UItableViewCell and the NSTimer loop is activated when the edit button is pressed. The edit button activates a function that has this [self.tableView2 setEditing:NO animated:YES]; and the NSTimer scheduledTimerWithInterval ... (The progress view animation). The issue here is that it the setEdit animation no longer animates it just pops into place. Once again, does anyone know of a solution for this?
Here is the code for question 2 (Both the code for the questions are very similar so if you can spot the problem here then it is likely I can use that solution to fix them both):
-(void)editTable{
[self.tableView2 setEditing:YES animated:YES];
iCount = 8;
iCount2 = 257;
forwardProgress = YES;
animationFlag = YES;
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.001f target:self selector:#selector(increaseAmount) userInfo:nil repeats:YES];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(cancelEdit)];
}
-(void)cancelEdit{
[self.tableView2 setEditing:NO animated:YES];
forwardProgress = NO;
animationFlag = YES;
iCount = 38;
iCount2 = 227;
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.001f target:self selector:#selector(increaseAmount) userInfo:nil repeats:YES];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editTable)];
}
-(void)increaseAmount{
float max = 38.0f;
float max2 = 257.0f;
float min2 = 227.0f;
float min = 8.0f;
if (animationFlag == YES) {
if (forwardProgress == YES) {
if (iCount <= max) {
iCount++;
// NSLog(#"iCount = %i", iCount);
}
if (iCount2 >= min2) {
iCount2--;
// NSLog(#"iCount2 = %i", iCount2);
}
if (iCount <= max) {
[tableView2 reloadData];
}
if (iCount2 >= min2) {
[tableView2 reloadData];
}
if (iCount == max) {
animationFlag = NO;
}
newFrame = CGRectMake(iCount, 33.0f, iCount2, 11.0f);
[tableView2 reloadData];
}else{
if (iCount >= min) {
iCount--;
NSLog(#"iCount = %i", iCount);
}
if (iCount2 <= max2) {
iCount2++;
NSLog(#"iCount2 = %i", iCount2);
}
newFrame = CGRectMake(iCount, 33.0f, iCount2, 11.0f);
if (iCount >= min) {
[tableView2 reloadData];
}
if (iCount2 <= max2) {
[tableView2 reloadData];
}
if (iCount == min) {
animationFlag = NO;
}
}
}
}
Any timer invalidates itself when it has finished unless it has repeats:YES. If that's the case, you need to invalidate it yourself at some point, ideally when all your conditions are met. In your case I think you'd want it when you're setting animationFlag == 0 (I presume that's when you want the timer to stop firing). Use something like this:
if ([myTimer isValid]) {
[myTimer invalidate];
myTimer = nil;
You also need to place that code in editTable at the beginning. That way, if a timer was running and someone started it again, it would first reset, and then begin again.Otherwise you have multiple timers all firing increaseAmount. You also need it anytime the user cancels the timer.
You probably have to invalidate the timer from your first animation. I suspect that you have two timers running and therefore the speed of the animation is doubled.
This is probably because of the reloadData call in your timer callback. A tableview cannot reload and animate at the same time.
I believe rgeorge is correct;
You don't get rid of the "myTimer" when you stop your animation. So even though you may not see the animation any more, increaseAmount is still being called over and over. Change the method to something like this:
-(void)increaseAmount:(NSTimer*)Timer;
Then, when you're done with increase amount, make sure to turn off the timer from within increaseAmount.
I'm working on a matching program for iPad and when a user selects a button an image is "uncovered" and then when the user selects a second button,another image is uncovered. I then programmatically check for a match and if not, revert both button images back to their initial state.
This is working fine except when a match is NOT made, the switch happens so fast that you do not have time to see what you have "uncovered". I tried to make it sleep but the image doesn't ever swap to the uncovered state... Thoughts?
The code for this is as follows:
//Take action on the tap of one of the buttons
if(isFirstSelection)
{
firstSelection = [(UIButton *)sender tag];
tempImageItem = [tileArray objectAtIndex:firstSelection];
tempImage = [tempImageItem tileImage];
firstSelectionName = [[NSString alloc] initWithString:[tempImageItem tileName]];
[(UIButton *)sender setImage:tempImage forState:UIControlStateNormal];
tempButton = sender;
isFirstSelection = NO;
}else{
secondSelection = [(UIButton *)sender tag];
tempImageItem = [tileArray objectAtIndex:secondSelection];
tempImage = [tempImageItem tileImage];
secondSelectionName = [[NSString alloc] initWithString:[tempImageItem tileName]];
[(UIButton *)sender setImage:tempImage forState:UIControlStateNormal];
//Two game pieces have been removed so check to see if they are a match
if([firstSelectionName isEqualToString:secondSelectionName])
{
//Match found
//do something
}else{
**//NO MATCH FOUND
[NSThread sleepForTimeInterval:3];
//Display the checker board pieces again
[(UIButton *)sender setImage:[UIImage imageNamed:#"originalImage"] forState:UIControlStateNormal];**
}
//Reset isFirstSelection Flag to YES for next selection
isFirstSelection = YES;
}
From what i understand you want to put the button's original image after 3 seconds so the user has the time to see what happened.
You should look at the NSTimer class to trigger that code
[(UIButton *)sender setImage:[UIImage imageNamed:#"originalImage"] forState:UIControlStateNormal];
in 3 seconds from now.
For "run this code in X seconds" I prefer:
[self performSelector:#selector(spinWheel:) withObject:[NSNumber numberWithUnsignedInt:0] afterDelay:delay];
no need to muck around with timer objects.
I'm producing a "brick" every second. When you tap it, it goes away. My problem is, when a second, or more, appear on the screen, tapping the previous one eliminates the most recent one, and can't be removed from the screen at all. The code I have is:
- (NSTimer *)getTimer{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector: #selector(produceBricks) userInfo:nil repeats:YES];
return timer;
}
-(IBAction) tapBrick {
//remove last brick
[bricks[count] removeFromSuperview];
//add to score
count++;
NSString *scoreString = [NSString stringWithFormat:#"%d", count];
score.text = scoreString;
}
-(void) produceBricks {
//determine x y coordinates
int xPos, yPos;
xPos = arc4random() % 250;
yPos = arc4random() % 370;
//create brick
bricks[count] = [[UIButton alloc] initWithFrame:CGRectMake(xPos,yPos + 60,70,30)];
[bricks[count] setBackgroundColor:[UIColor blackColor]];
[bricks[count] addTarget:self action:#selector(tapBrick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:bricks[count]];
}
I know it has to do with the bricks[count] removeFromSuperview line being count is always increasing. How would I reference to the brick in the array that's being clicked instead of the current one?
If you add a sender argument, it'll automatically get set to the control which sent the action, so you can do something like the following:
-(IBAction) tapBrick:(id)sender {
[sender removeFromSuperview];
}
Make the method tapBrick:(UIButton *)brick and you'll get a reference to the brick that was tapped as the argument.
Also, I strongly advise against using a C array for this. It's just asking for memory management trouble. Better to use an NSMutableArray and make sure to follow the memory management rules. With the code you've posted, your brick objects will never be released, and instead they'll just keep eating memory until your app exhausts its supply.
Views have a tag property for a reason — So you can identify views of the same type in the same view controller. Consider setting a unique tag for each of these bricks, and then removing the particular one you want to.