I am uploading a picture to a server. I am using a UIAlertView, AlertView1, which asks the user if he/she wants to upload the picture. If Yes, a second alertview, AlertView2 will show with a progress bar and disappear after the upload is complete.
So, once the user clicks on Yes in AlertView1, I call show AlertView2 and call the method [self uploadPhoto]. So the problem comes that even before AlertView2 has time to show up, the CPU intensive uploadPhoto is running and it delays the AlertView2 from showing up for several seconds. It seems like the AlertView2 finally shows a ways through the uploading process.
How do I start the uploading process only once AlertView2 has shown?
Here is the code
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Detectss AlertView1's buttonClick
[self.alertView2 show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
//Launches when AlertView1 is dismissed
[self UploadPhoto]
}
-(void)uploadPhoto
{
//CPU/Network Intensive Code to Upload a photo to a server.
}
Set your controller as the delegate of the second alert view, and upload the photo when you receive the didPresentAlertView: message from your second alert.
Use multi-threading to dispatch uploadPhoto to another thread. That way you won't block the main thread while performing costly network operations.
dispatch_queue_t queue = dispatch_queue_create("upload queue", NULL);
dispatch_async(queue, ^{
[self uploadPhoto];
dispatch_async(dispatch_get_main_queue(), ^{
//dismiss alertview2 here
});
});
Call your method [self uploadPhoto] after calling [self.alertView2 show] with a timer using -
[self performSelector:#selector(uploadPhoto) withObject:nil afterDelay:0.3];
It will call your method after a 3 seconds gap after your alert appears.
Related
I have this really simple flow, where a notification is received when the app is in killed mode and the user taps on that notification.
Logic will append two controllers to the array of current viewControllers in the rootNavigationController. So, if first view was the usual screen for my app, in this case it will be first>second>third.
If you open the app with the app-icon, you'll only see the first view.
Issue is, when you tap on the notification, third view has an async API call, which is invoked from viewDidLoad. The SVProgressHUD is used for ux purpose, which is hidden in the completion block of the API. Sometimes, the progressHUD keeps on rotating and nothing happens. It gets stuck, but If I set the breakpoint in the API call method itself, everything works fine every time.
Not able to figure out, how to debug this issue. Tried pausing the execution when it gets stuck, but no relevant thread seems to be in the list.
CODE in third VC:
- (void) fetchDataPoints
{
//[SVProgressHUD show];
[[ServerHandler sharedInstance]fetchDataFromServerWithApiUrlString:GET_ALL_DATAPOINTS methodType:#"GET" httpBodyData:nil contentType:nil otherHeaderFields:nil queryStringParams:nil withCompletionBlock:^(BOOL success, NSData *responseData, NSError *error, NSHTTPURLResponse *response,NSDictionary *responseDict)
{
[SVProgressHUD dismiss];
if (success)
{
}
}];
}
CODE: push notification
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateInactive)
{
ChildViewController *childList = ViewControllerWithSBID (#"DATASB",#"ChildScreenID");
DetailViewController *detailVC = ViewControllerWithSBID(#"DATASB", #"DetailScreenID");
NSMutableArray *viewControllers = [self.rootNavigationController.viewControllers mutableCopy];
[viewControllers addObjectsFromArray:#[childList,detailVC]];
self.rootNavigationController.viewControllers=[[NSArray alloc]initWithArray:viewControllers];
}
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];
I am using a UIWebView and don't want the navigation bar to appear unless the user taps anywhere on the screen that isn't a link.
So I have this code to display the navigation bar after a delay:
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
....
[self performSelector:#selector(showNavigationBar) withObject:self afterDelay:0.2];
}
I'm not calling showNavigationBar immediately when the tap handler is invoked because the user might have tapped on a link in which case the tap hander is called before UIWebView shouldStartLoadWithRequest, so if I hid the navigation bar in shouldStartLoadWithRequest it would flash momentarily onto the screen.
So instead I set it to display after a delay which gives time for the following code to execute within shouldStartLoadWithRequest (and if the user didn't tap on a link shouldStartLoadWithRequest isn't called and the navigation bar is displayed, as it should be in that case).
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:nil];
...
However this isn't working, I've increased the delay time to several seconds and can confirm cancelPreviousPerformRequestWithTarget is getting called before the navigation bar has been displayed, but when the specified time elapses the bar displays. cancelPreviousPerformRequestWithTarget is having no effect.
Does anybody know why its not working?
Your perform doesn't match your cancel. In the perform you're passing self as the object:
[self performSelector:#selector(showNavigationBar) withObject:self afterDelay:0.2];
In the cancel you're passing nil as the object:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:nil];
They don't match, so the delayed perform should not be canceled.
In the documentation of that + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument method there is this sentence :
This method removes perform requests only in the current run loop, not all run loops.
If I'm interpreting it correctly it would mean that you need to cancel your action in the same run loop that you launched it. Which is clearly not what you want to do.
A way to go around this would be to have a flag that showNavigationBar would have to check to see if it should proceed or abort.
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:self];
That worked for me ;)
Not sure why but works like a charm for me.
dispatch_async(dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
});
i've an app that when start control updates and other things. If the app find some updates they will ask user if this updates have to be done. If user select YES i want that a spinner appear on main screen until update finish. But when i tap YES my alert view doesn't disappear and remain on screen until update is finished.
Is it possible to create a thread that run on the main thread and stop when update in finished?
Thanks
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
If the methods
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
are executed synchronously (that means that they return only after having processed completely), so:
[self hideActivityViewer];
is executed only at the very end.
A simple approach to this is scheduling the execution of your methods on the main thread:
[self performSelector:#selector(downloadControlAndUpdatePoi) withObject:nil afterDelay:0];
....
[self hideActivityViewer];
so that those methods are executed only after control has returned to the main loop and the UI has been updated.
Otherwise, you could use:
+ detachNewThreadSelector:toTarget:withObject:
from NSThread, to do more or less the same. In this case I would suggest creating a wrapper method for all of your dowloadAndUpdate... methods, but keep in mind that you can't update the UI from a secondary thread.
In both cases, you should take some care about synchronizing the download... operations with the rest of your workflow after removing the alert view.
I'm using GCD for background downloading in my Tab Bar app.
First step is to do some background downloading in -viewWillAppear: (to setup some basic data before the view is loaded).
Second step is to the rest of the background downloading in -viewDidAppear:
For some reason, the dispatch block in -viewDidAppear: gets called before the dispatch block in -viewWillAppear:.
This only happens once after loading the application for the first time switching to the tab with the GCD background methods. Switching to another tab and then switching back to the tab with the GCD background methods. The third (and all the rest subsequent times) time I'm switching back it's works as expected (-viewWillAppear: firing first and then -viewDidAppear:).
Here are excerpts of my code (-viewWillAppear: and -viewDidAppear:):
-viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
DLog(#"viewWillAppear method running");
[super viewWillAppear:animated];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[self setDiskCareerIds:[CareersParser idsFrom:#"disk"]];
[self setDownloadedCareerIds:[CareersParser idsFrom:#"web"]];
DLog(#"diskCareerIds after being set in viewWillAppear: %#", [self diskCareerIds])
DLog(#"downloadedCareerIds after being set in viewWillAppear: %#", [self downloadedCareerIds])
if ([[self downloadedCareerIds] isEqualToArray:[self diskCareerIds]]) {
DLog(#"viewWillAppear: ids equal, loading careers from disk.");
self.careers = [CareersParser loadCareersFromDisk];
dispatch_async(dispatch_get_main_queue(), ^{
[self.table reloadData];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
});
}
});
//[self downloadData];
}
-viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
DLog(#"viewDidAppear method running");
[super viewDidAppear:animated];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
if (![[self downloadedCareerIds] isEqualToArray:[self diskCareerIds]]) {
DLog(#"ids not equal, saving careers to disk.");
dispatch_async(dispatch_get_main_queue(), ^{
[self showLoadingView];
});
[CareersParser saveCareersToDisk];
self.careers = [CareersParser loadCareersFromDisk];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.table reloadData];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
[self removeLoadingView];
});
});
//[self download3];
//[self downloadData];
}
Check the debug log at Pastie.
Well, you're printing that log message in that first block (the one scheduled in viewWillAppear:) after it has done a bunch of parsing, not when it actually starts executing.
The thing is that global queue is a concurrent queue. So even though you are scheduling that first block first, it's not surprising that it sometimes falls behind the other block which is executing concurrently with it.
One simple answer would be to create a serial queue, and then you'll be sure the first block completes before the second one is executed. That seems to be what you want, right?