I am trying to have an object disappear from the view after a certain time after a button is tapped. I'm a little confused with how to get the object to do something a certain after its tapped. I am not sure if I should use a run loop or NSTimer, and even if I know what to use Im still confused on what to do to make something happen a certain time after the button is tapped.
In your button pressed method you can use:
[self performSelector:#selector(myMethod) withObject:nil afterDelay:3];
And declare method with logic you want to run:
-(void) myMethod
{
//TODO: your logic goes here
}
You can even pass parameter to your method if you want (withObject argument).
You can use NStimer Also
NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(afterTapped:)
userInfo:nil
repeats:NO];
and the create action to be done for afterTapped
-(void)afterTapped:(id)sender{
//do something
}
Related
I have a number of methods that insert data into database table.These methods are called one after the other. While the insertion process in on, I want my UI to show appropriate message.
eg-
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"]];
[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"]]
[dbManager insertIntoTable_ZDAILY_MENU_RECIPE_WithData:[resultObj objectForKey:#"daily_menu_recipe"]];
So, when I say
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"]];
label on ui must show 'Updating Added Food Items...'
then when next line comes
[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"]];
label should read as 'Updating Usda Food' and so on.
I want the text on label to remain at least for a second. I have used 'dispatch_after' but still not able to get the desired effect.
Is there a way to insert delays between method calls and also to make sure that second method is called only when first one is completed.
Use the Command Pattern its a well known design pattern, basically its a list and each item has state, when one finishes the next one starts.
this case will allow you to not have a completion block inside a completion block inside a completion block.
You can make use of completion block :)
change your method to take a completion block as an arguement and once done with the job execute the completion block :) and in completion block start the next method call :)
You can modify your method signature as follow
-(void)insertIntoTable_ZADDED_FOOD_ITEMS_WithData:(NSData *)data andCompletionBlock:(void (^)())completionBlock{
//do whatever you want to do here
//once done simply say
completionBlock();
}
and you can call this method as :)
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"] andCompletionBlock:^{
//call next method
//[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"] andCompletionBlock:^{
}];
}];
And in case you dont wanna send any completion block simply pass nil :)
But in that case dont forget to add the check as if(completionBlock) before calling completionBlock() app will crash in case it is passed as nil :)
on a second thought :)
if your method insertIntoTable_ZADDED_FOOD_ITEMS_WithData: is synchronous :) you can change the label directly like
yourLabel.text = #"Updating Added Food Items...";
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"]];
yourLabel.text = #"Doing something else ...";
[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"]];
yourLabel.text = #"Doing something else again...";
[dbManager insertIntoTable_ZDAILY_MENU_RECIPE_WithData:[resultObj objectForKey:#"daily_menu_recipe"]];
No need of any delay or completion block here buddy :)
Finally if your concern is methods are executing much faster and label wont even appear for a fraction of a second and you intentionally want to add a delay between each method :)
-(void)insertIntoTable_ZADDED_FOOD_ITEMS_WithData:(NSData *)data andCompletionBlock:(void (^)())completionBlock{
//do whatever you want to do here
//once done simply say
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
completionBlock();
});
}
Hope my answer helped you :) Happy coding :)
This will be helpful for what you required.I did this successfully.
- (void)button_circleBusy:(id)sender {
firstButton.enabled = NO;
// 60 milliseconds is .06 seconds
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToSecondButton:) userInfo:nil repeats:NO];
}
- (void)goToSecondButton:(id)sender {
firstButton.enabled = YES;
secondButton.enabled = NO;
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToThirdButton:) userInfo:nil repeats:NO];
}
I hope it will work for you also!!
When you are not running you function in background then it should call one by one (after first finish). You don't need to bother about completion of first then start next.
And if you want to show message notification to hold for a sec on your screen then remove label with delay about a sec.
You can also delay your method call for a sec and so.
[self performSelector:#selector(removeMessageLabel) withObject:nil afterDelay:1.0];
Here "afterDelay" is responsible to make a pause for that particular method which is declared inside but compiler will move for further line execution.
In the sample tutorial I found, I noticed a timer made using NStimer class: it was implemented directly into viewController.m
I tried to make it as "separate" obj class in it's own timer.m and relative header.
This is what i got
#import "Timer.h"
#implementation Timer
-(void) startTimer{
seconds = 31;
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(subtractTime:) userInfo:nil repeats:YES];
NSLog(#"%i",seconds);
}
-(void)subtractTime: (Timer*)myTimer{
seconds--;
NSLog(#"seconds %i",seconds);
if (seconds == 0) {
[myTimer invalidate];
}
}
#end
According to the output in NSLog, the countdown itself works perfectly; my issues start when i try to display it into a label using the form label.text = into the viewController.m
(here's just the method implementation part)
(Xcode gives me no error bout #implementation part both in Timer.h and viewController.h, also project build runs ok but the countdown into the label is locked to 0)
-(void)setupGame{
count = 0;
scoreLabel.text = [NSString stringWithFormat:#"Score:\n%i",count];//these strings are to make a score label increasing of 1 every time a button's pressed
Timer *newTimer = [[Timer alloc]init]; //i create a new obj of my Timer class
[newTimer startTimer];
[newTimer subtractTime:(Timer*) myTimer]; //i set the methods i created
timerLabel.text = [NSString stringWithFormat:#"%i",seconds]; //??? i don't know which var put here, actually, i cannot get here the refreshing seconds to make timerLabel changing
NSLog(#"newTimer %#",myTimer);//just a try to see this output, it's not what i need of course
}
What am i doing wrong?
There are a couple of issues here. Firstly make Timer an instance variable of the view controller as when you do this:
-(void)setupGame{
// blah
Timer *newTimer = [[Timer alloc]init];
// blah
}
The Timer object will be destroyed when this method returns, as the object goes out of scope.
The second problem is that you need to update the UI control when the time changes, so perhaps pass a reference to the UI control (UILabel or whatever it is), and store this as an instance variable of the Timer class, so the timer object can update it itself.
In this particular case I don't see the value of a separate Timer object as it would be easier to implement what you want just within the view controller class.
As Trojanfoe says, you are over-complicating this. Make your view controller create the timer and be done with it.
If you are bound and determined to create a custom class to manage a timer then you have a bunch of work to do.
I would NOT pass the label to the Timer object, as that breaks the encapsulation of the view controller. You should always treat a view controller's views as private to that view controller.
Instead, here's what I would do:
You need to add a Timer instance variable to your view controller, as Trojanfoe says.
You need to define a TimerProtocol. In that protocol, define a "timerFired" method, that would include the remaining time value as a parameter. You probably also want a "timerFinished" method
When you create a Timer object, set yourself up as the delegate of that timer.
Rewrite your Timer object's subtractTime method to send a message to the delegate with the remaining time value. Then in the view controller's timerFired method, update the label.
In your view controller's timerFinished method, nil out the instance var to the Timer object so it will be deallocated, since you then done with it.
Again, this is an over-complicated solution that does not add any value. The only reason to do it this way is as a learning exercise, but it is not a good design decision.
Hi i am developing an iphone applicaiton, in my application i have a scenario like a UItabbar controller having two UIviewcontrollers. in each view controller i have seperate nstimer.
I want to know that how to stop all timers from one of the view controller.
My timer code as follows
-(void)startTimer
{
if (_timer == nil)
{
_timer = [NSTimer scheduledTimerWithTimeInterval:10.0f
target:self
selector:#selector(setProgress)
userInfo:nil
repeats:YES];
}
}
- (void)stopTimer
{
if (_timer != nil)
{
[_timer invalidate];
_timer = nil;
}
}
-(void)setProgress
{
[self stopTimer];
[self initmethod];
}
if i navigate to any other page from one of the uiviewcontroller i just stoped and invoks by callback method. if i navigate to other page from a one of the uiviewcontroller means i want to stop all timers in all three uiviewcontroller.
Subclass uitabbarcontroller and add a method 'stopAllTimers'. That method will iterate through its array of viewControllers, sending each a 'stopTimer' message.
of cause, you could use NSNotification's in all the VC's
but if your repeating timers are mostly needed for updates of the VC's, the better way would be to save "last seen" time for each of them (in viewDidDisappear) and check if that time saved is distant enough in viewWillAppear method
There is not a direct way to do this.
But, you may consider using a shared bool variable ,say shouldStopTimer, which can be accessed in all viewControllers.
Add a check in your "setProgress" method if shouldStopTimer is NO.
Invalidate the timer if you find shouldStopTimer as YES in you "setProgress" method.
Else
You can use NSNotification to invalidate the timers in their respective viewControllers.
I know that these NSTimer questions have come up numerous times, however since none seem to involve executing blocks that change the UI, I figured this is still an original question.
I have a subclass of UIButton that, for convenience sake (me, coming from an Android background), has an onClick and onHoldClick function. onClick simply takes a block and executes it in the selector that responds to UIControlEventTouchUpInside. The click function works great. For example:
[myButton setOnClick:^{
NSLog(#"clicked");
}];
The hold click functionality is not working so well.
[myButton setOnHoldClick:^{
NSLog(#"still holding click...");
}];
This listens for the UIControlEventTouchDown event, and performs a task after a delay:
- (void)clickDown:(id)sender
{
isClicked = YES;
[self performSelector:#selector(holdLoop:) withObject:nil afterDelay:delay];//For the sake of the example, delay is set to 0.5
}
The hold loop runs a repeated timer on another function, which handles the block execution (the timer variable is an NSTimer declared in the header file):
-(void)holdLoop:(id)sender
{
[self cancelTimers];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(death:) userInfo:nil repeats:YES];
}
-(void)death:(id)_delay
{
if (isClicked)
{
_holdBlock();
}
else
{
[self cancelTimers];
}
}
The block that executes changes the value of a float, which is used to update the value of a label, which is then redrawn.
The first time the hold click event occurs, this works great. After that, it seems like timers don't get canceled, and new timers are still added. This is what my cancelTimers function looks like (calls here are retrieved from a collection of the other questions on this topic):
-(void)cancelTimers
{
[_timer invalidate];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(death:) object:nil];
}
What am I doing wrong, and how do I fix it?
Edit
I do, in fact, already have the function that responds to touch up inside:
- (void)clickUp:(id)sender
{
isClicked = NO;
[self cancelTimers];
_clickBlock();
}
Furthermore, I have realized that the issue comes from an unhandled cancel event. Is there a reason why iOS would auto-cancel my long press?
Solved
Since the block redrew the UI, it was also redrawing the buttons (and resetting their functionality). This event was causing a cancel event to be called on the button - which was not handled. Adding the following:
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchCancel];
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchUpOutside];
-(void)cancelClick:(id)sender
{
isClicked = NO;
[self cancelTimers];
}
As well as reconsidering what changes are made in the block, has gotten me past this issue.
As I understood from the comments and the code, the clickDown: is called for UIControlEventTouchDown so isClicked is set to YES when the first time the button is touched down. You need to add a selector to the event UIControlEventTouchUpInside. It's called when the user lifts his finger while being iside the bound of the button. Inside that method, set isClicked to NO.
Everytime I am finished with my NSTimer, I want to invalidate it and create a new interval but it keeps the old interval as well as new interval. I want to invalidate NSTimes once I click the offButton. The timer stops printing "Working" but when I call my method with a different interval, it prints "Working" for both intervals.
My code is something like this:
-(void) fireTimer{
NSString *textValue = [sliderLabel text];
float value = [textValue floatValue];
[NSTimer scheduledTimerWithTimeInterval:value target:self selector:#selector(vibrate:) userInfo:nil repeats:YES];
}
- (void) vibrate:(NSTimer*)timer {
if(_offButton.selected){
[timer invalidate];
timer=nil;
}
NSLog(#"Working");
}
You aren't following the MVC design pattern by getting your values directly from the UITextField. Those values should be stored in a model object, with the MVC pattern being used to get any new values from the text field into the model. Your current implementation is very delicate and will break in the slightest breeze. It also requires this code to have access to the UI elements, which is very inflexible; it will be better to give it access to just the model object.
Store the NSTimer * as an instance variable, and note that if you are using ARC then the NSTimer retains the target (!!) so make this instance variable __weak to break the retain-cycle.
If you want your timer to repeat then there is no need to reset it at all; this only needs to be done if the user changes the time (see point 1!).
Move the if (button_selected) do_vibrate; code into the timer fired method.
The invalidation code that you use itself is correct. But it would be easier for you to keep a reference to your timer as an ivar or property.
In that case you would definetly avoid making multiple instances of a timer.