Delay a method call in objective-c - ios

I know this can be done by using:
[self performSelector:#selector(myMethod) withObject:nil afterDelay:3.0]
However, the problem is that I want only 1 method call do be done.
With this function the calls will stack on top of each other. I want to make a call and if another call is made the first one will be dismissed. Ideas?

Once the method is executing then there is no way of stopping it.
But you can cancel if it is not fired. You can do something like this
//.... your code
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(myMethod) object:nil];
[self performSelector:#selector(myMethod) withObject:nil afterDelay:3.0];
//.... your code
In this way you can cancel previous perform request only if the myMethod is not being fired.

In the Code Snippet Library in Xcode you can find one called GCD: Dispatch After, which looks like this:
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){
<#code to be executed on the main queue after delay#>
});
Pretty self-explanatory.

EDIT: Now that I know that you want to only use the most recent, you could instead use:
[self cancelPreviousPerformRequestsWithTarget:self selector:#selector(myMethod) object:nil];
See this link for more info.
ORIGINAL POST:
You could just have a BOOL that is set to NO when it reaches that section and is then reset to YES after the method is performed.
So, for example, it would look something like:
if (boolVal) {
boolVal = NO;
[self performSelector:#selector(myMethod) withObject:nil afterDelay:3.0];
}
then in your myMethod, have:
boolVal = YES;

You should perform this selector in some other thread to avoid stack as you asked.
use
[self performSelector:(SEL) onThread:(NSThread *) withObject:(id) waitUntilDone:(BOOL)];
In that selector you can add delay what ever you want. As this process will run in separate thread so will not stop others for the delay

Related

how to use MBProgressHUD to pause the screen when the loading animating

I have a problem.
I use MBProgressHUD in my tableviewcontroller.
I want that the MBProgressHUD loading animating then user can't touch anything and wait to get server data to show on the tableview.
I want to pause screen until hud hideAnimated.
Thanks!
This is my code:
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.listTableView animated:YES];
hud.label.text = #"Loading";
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self getInfo:nil];
});
hud.userInteractionEnabled = YES;
[hud hideAnimated:YES afterDelay:2];
You shouldn't use [hud hideAnimated:YES afterDelay:2] in this case. There is no point in dispatch_after either.
The thing is that your request may finish really quick (like 0.1 sec) and the hud will be still shown for another 1.9 sec. Or it may stay pending for a really long time, like 30 seconds or whatever timeout you set, so your code will hide the hud when it should actually be shown.
What you should do is to call completion blocks (or delegate methods) when the request is finished (successfully or not).
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = #"Loading";
[self getInfoWithSuccess:^{
[hud hideAnimated:YES];
//your code here
} failure:^(NSError *error) {
[hud hideAnimated:YES];
//show error message if needed
}];
Request method itself:
- (void) getInfoWithSuccess: (void(^)()) success failure: (void(^)(NSError *)) failure {
//do async request here and call success or failure block when it's finished
}

Sleep function in iOS with Spinner

I have a spinner function to start and stop from beginning of process to end. But since process takes milliseconds, i can't really keep spinner turning.
[NSThread sleepForTimeInterval:2.0]; //method 1
sleep(2); //method2
Then i used Sleep methods to standby the code and let spinner turn, but it stops the Thread, then spinner will stop as well. This is the code:
if (indexPath.row == 1)
{
[MBProgressHUD showHUDAddedTo:self.view animated:YES]; //spinner starts
EmployeeDataSource *aaa=[[EmployeeDataSource alloc]init];
[aaa Logout: ^(BOOL results){
if(results == YES){
[NSThread sleepForTimeInterval:2.0]; //sleeping thread
//if logout succesfull go to login page
LoginViewController *lv = [self.storyboard instantiateViewControllerWithIdentifier:#"loginsb"];
[self presentViewController:lv animated:NO completion:nil];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES]; //spinner ends
}
else{
NSLog(#"logout not succesfull");
}
}];
I want [MBProgressHUD showHUDAddedTo:self.view animated:YES]; to work for 2 seconds at least, but it ends in less than a second since normal process is fast? How can i extend this time, sleep method seems not suitable. Do you have any idea? Thank you.
i think you should do your stuff after some delay.
for that please use this
[self performSelector:#selector(method) withObject:nil afterDelay:1.0];
here Delay is in second, so you can set it accordingly.
Or you can go with this as well.
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//Do you stuff here...
});
May this help you.

dispatch_after CLLocationManager didUpdateLocations or didFailWithError

I have this piece of code managing a "pull to refresh" which updates the location of the device:
[self.glassScrollView.foregroundScrollView addPullToRefreshWithActionHandler:^{
int64_t delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"Pulled");
[weakSelf.locationManager startUpdatingLocation];
[weakForegroundScrollView.pullToRefreshView stopAnimating];
});
}];
When it STARTS updating the location the animation stops, but NOT when it actually updates the location or returns an error for it. This creates the awkward situation in which it appears to have updated the location and the UI (the spinning wheel animation disappeared), but it is still processing it through the CLLocationManagerDelegate methods.
So, what's the best way to "connect" the locationManager:didUpdateLocations and the locationManager:didFailWithError methods with this queue?
I was thinking about adding some sort of "listener" in the code above waiting for a certain method to be called inside the CLLocationManagerDelegate methods.
Here's also my locationManager:didUpdateLocations method.
(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
NSLog(#"didUpdateLocations");
CLLocation *currentLocation = [locations lastObject];
// do stuff in the UI
// I stop updating the location to save battery
[self.locationManager stopUpdatingLocation];
}
Thanks!
Ok. This code might help:-
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(whenCompleted) name:#"whenCompleted" object:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"whenCompleted" object:nil];
}
-(void)whenCompleted{
[weakForegroundScrollView.pullToRefreshView stopAnimating];
}
When all the methods are executed, before the end of the last method, post the notification:-
[[NSNotificationCenter defaultCenter]postNotificationName:#"whenCompleted" object:nil];
Then it will stopAnimating when the progress is completed.
You handle the pull request with something like:
[self.glassScrollView.foregroundScrollView addPullToRefreshWithActionHandler:^{
int64_t delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"Pulled");
[weakSelf.locationManager startUpdatingLocation];
[weakForegroundScrollView.pullToRefreshView stopAnimating];
});
}];
I'm assuming that one second delay is because you want to hide the pull to refresh after one second, even though the refresh is still in progress.
What I'd suggest is:
[self.glassScrollView.foregroundScrollView addPullToRefreshWithActionHandler:^{
[weakSelf.locationManager startUpdatingLocation];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);, dispatch_get_main_queue(), ^(void){
[weakSelf.glassScrollView.foregroundScrollView setContentOffset:CGPointZero animated:YES];
});
}];
This keeps the UIActivityIndicatorView spinning, but scrolls it off screen. If you pull it down again, you'll see it's still going. That's an easy way to keep the activity indicator going, but getting it out of the user's way.
And then, as Ricky says, only when the location is retrieved would you stopAnimating (or post a local notification to the [NSNotificationCenter defaultCenter] so your observer can do that). But do not just try to stopAnimating after a set period of time, but rather tie that to the actual completion of whatever asynchronous task you were performing.

How can I add a delay before it continues

Is there a way in the normal obj-c or cocos2d to make a delay inside a if-else block?
Like
if ([self isValidTileCoord:cTileCoord] && ![self isWallAtTileCoord:cTileCoord])
{
[self addChild:circle0];
//wait two seconds
//perform another task
}
Just a simple lag to wait between two tasks, or stalling an action. Is there any simple way to do this?
There are many ways to do this. I would use GCD
if ([self isValidTileCoord:cTileCoord] && ![self isWallAtTileCoord:cTileCoord])
{
[self addChild:circle0];
//wait two seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
//perform another task;
});
}
You can use the performSelector: withObject: afterDelay: method to delay tasks:
if ([self isValidTileCoord:cTileCoord] && ![self isWallAtTileCoord:cTileCoord])
{
[self addChild:circle0];
//wait two seconds
[self performSelector:#selector(continueTask) withObject:nil afterDelay:2.0];
}
And in the selector method:
-(void)continueTask
{
//perform another task
}
Inside the cocos2d you can also run an Action on the node like,
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:2.0],[CCCallFunc actionWithTarget:self selector:#selector(anothertaks)],nil]];
Perform selector will also work but the problem is that all the schedulers of cocos2D get paused when the app go to background but on the other side perform selector will still count the time, so some time it create task,animation,etc syncing problems.
Perform selector:
[self performSelector:#selector(continueTask) withObject:nil afterDelay:2.0];

Dealing with two screens and one activity indicator in iOS

I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];

Resources