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.
Related
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
}
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.
I need a way to perform a similar function like the one below, but instead of waiting 3 seconds, it waits for activity indicator to be hidden [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;. Any tips or suggestions are appreciated.
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// lines of code
});
How about you use Key-Value Observing:
Declare context
static int NetworkActivityContext;
Add your observer
[[UIApplication sharedApplication] addObserver:self forKeyPath:#"networkActivityIndicatorVisible" options:NSKeyValueObservingOptionNew context:&NetworkActivityContext];
Implement the KVO callback
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &NetworkActivityContext) {
BOOL isNetworkActivityVisible = [UIApplication sharedApplication].networkActivityIndicatorVisible;
if(!isNetworkActivityVisible){
//Do whatever work you need to do now that it's hidden
}
}
}
Do not rely on the activity indicator to determine the flow of the app. Where you set [UIApplication sharedApplication].networkActivityIndicatorVisible = NO you should also either fire a notification or otherwise indicate to a wider audience the activity has ended. Then listen for that notification and do what you need to do elsewhere.
If you are setting networkActivityIndicatorVisible inside the same class, or if you are performing a network call and can put what you need in the completion block or delegate callback, you should do that instead.
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
As we know there are some limitations for applications which running in background mode.For example, the NSTimer doesn't work. I tried to write a "Timer" like this which can work in background mode.
-(UIBackgroundTaskIdentifier)startTimerWithInterval:(NSTimeInterval)interval run:(void (^)())runBlock complete:(void (^)())completeBlock
{
NSTimeInterval delay_in_seconds = interval;
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, delay_in_seconds * NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// ensure the app stays awake long enough to complete the task when switching apps
UIBackgroundTaskIdentifier taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
completeBlock();
}];
NSLog(#"remain task time = %f,taskId = %d",[UIApplication sharedApplication].backgroundTimeRemaining,taskIdentifier);
dispatch_after(delay, queue, ^{
// perform your background tasks here. It's a block, so variables available in the calling method can be referenced here.
runBlock();
// now dispatch a new block on the main thread, to update our UI
dispatch_async(dispatch_get_main_queue(), ^{
completeBlock();
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
});
return taskIdentifier;
}
I called this function like this:
-(void)fire
{
self.taskIdentifier = [self startTimerWithInterval:10
run:^{
NSLog(#"timer!");
[self fire];
}
complete:^{
NSLog(#"Finished");
}];
}
This timer works perfect except that there is one problem. The longest period for background task is 10 minute.(Please refer to the NSLog in startTimerWithInterval).
If there any way to make my timer work more than 10 minutes? By the way, my application is a BLE application, I set UIBackgroundModes to bluetooth-central already.