I am trying to make sure the singleton finishes executing before the UI gets updated. I tried using alertView to show please wait and dismiss it on completion but it did not work. I then tried to use notification centre to update a label in the UI once the adapter changes state.
What is the best way to connect on button click and wait for the the singleton to complete? Should I move _adapter and _transporter inside the dispatch_get_main_queue ?
- (IBAction)connectAdapter:(id)sender {
// connnect to adapter
dispatch_queue_t myQueue = dispatch_queue_create("my queue", NULL);
dispatch_async(myQueue, ^{
[BLEManager sharedInstance];
NSLog(#"DID RUN BLE MANAGER");
_adapter = BLEManager.sharedInstance.adapter;
_transporter = BLEManager.sharedInstance.transporter;
dispatch_async(dispatch_get_main_queue(), ^{
//update your UI
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onAdapterChangedState:) name:AdapterDidUpdateState object:nil];
});
});
}
Update:
If I added in an alert view with a spinner like the code below, would i call it inside the async queue or before i instantiate myQueue
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
message:#"Please wait\n\n\n"
preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.center = CGPointMake(130.5, 65.5);
spinner.color = [UIColor blackColor];
[spinner startAnimating];
[alert.view addSubview:spinner];
[self presentViewController:alert animated:NO completion:nil];
I can then dismiss the alert view inside the get_main_queue using:
[alert dismissViewControllerAnimated:YES completion:nil];
Related
In my code, I initialize a UIActivityIndicator, and add it as a subview, but it never appears. What's going wrong?
-(void) update {
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityView.center=self.view.center;
[self.view addSubview:activityView];
[activityView startAnimating];
/** Stuff to access to server, this stuff works correctly.*/
[activityView stopAnimating];
}
As you are starting and stoping the activity as same runloop so it will not get change to update the UI.You are doing all server stuff on same synchronously than you are blocking the runLoop and do not giving time to activity indicator to start.You should start server stuff on next runLoop so activity indicator will get time to start.So use performSelector with delay 0.0 it will start the activity indicator and than perform your server stuff on immediate next runloop and than stop the activityInddicator
-(void) update {
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityView.center=self.view.center;
[self.view addSubview:activityView];
[activityView startAnimating];
//Use performSelector with 0.0 so activity indicator starts
[self performSelector:#selector(thisWillStartAtNextRunLoop) withObject:nil afterDelay:0.0];
}
-(void)thisWillStartAtNextRunLoop{
/** Stuff to access to server, this stuff works correctly.*/
[activityView stopAnimating];
}
Edit execute it on main thread as all UI updation must be on main thread
Try
-(void) update {
dispatch_async(dispatch_get_main_queue(), ^{
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityView.center=self.view.center;
[self.view addSubview:activityView];
[activityView startAnimating];
});
/** Stuff to access to server, this stuff works correctly.*/
}
I have a method to retrieve data from an external url, load it into an array from JSON format, and populate a UITableView. It works fine, but there is no indication to the user that something is happening while the data is downloaded.
- (void)viewDidLoad
{
[super viewDidLoad];
[self retrieveDataC];
}
Here is the code that I tried for viewDidLoad which adds a spinner animation while downloading. I'm attempting to put retrieveDataC on a background thread and when it completes, I would like the view to continue executing as though I didn't implement the multi-threading in the example above.
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self retrieveDataC];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
});
});
}
The loading spinner displays correctly for a brief moment, however after the process is done I'm left with a blank table as though I have not called [self retrieveDataC] to begin with. Any suggestions, advice? Am I setting up the background process correctly?
Thank you
EDIT:
Here's what ended up working -
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self retrieveDataC];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
[self.collectionView reloadData];
});
});
}
Do you call [self.tableView reloadData] after you received the data?
I'm trying to get a UIActivityIndicatorView to show on screen while my CSV import method is running, but I can't get it right. With the code below, the ActivityIndicator subview shows for a second or so then disappears, even if the import operation is still running. How can I make it stay on screen until the NSOperationQueue is finished? I'm using iOS 7.1 on my test device.
User taps 'Yes' on an UIAlertView to import the data:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if(buttonIndex == 0){
//clicked Yes
[self loadingSpinner];
operationQueue = [NSOperationQueue new];
NSInvocationOperation *importOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(importCSVData:) object:self.importURL];
[operationQueue addOperation:importOperation];
}
else if(buttonIndex == 1){
//clicked No
}
}
Method to show a UIActivityIndicatorView on top of everything else on screen:
-(void)loadingSpinner{
self.overlayView = [[UIView alloc] init];
self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
UIView *topView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
self.overlayView.frame = [UIScreen mainScreen].bounds;
}
else{
self.overlayView.frame = topView.frame;
}
[self.overlayView setUserInteractionEnabled:NO];
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.spinner.center = self.overlayView.center;
[self.overlayView addSubview:self.spinner];
[self.spinner startAnimating];
[topView addSubview:self.overlayView];
}
At the end of the import operation to remove the activity indicator:
[self.overlayView removeFromSuperview];
Use addOperationWithBlock: method on NSOperationQueue with which you will have more control. You would edit your code like this to use block version,
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self loadingSpinner];
}];
[self importCSVData:self.importUrl];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.overlay removeFromSuperView];
}];
}];
You can also use GCD to implement this task.
Create dispatch queue:
dispatch_queue_t queue = dispatch_queue_create("Other Q", NULL);
or you can use the main queue like:
dispatch_get_main_queue()
Finally replace your code with the following:
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(),^{
[self loadingSpinner];
});
[self importCSVData:self.importURL];
});
You might want to check for the title of you button clicked on your UIAlertView.
To do so change this:
if(buttonIndex == 1)
With this:
if([[alertView buttonTitleAtIndex:buttonIndex]isEqual:#"YES"])
Let me know if this helps.
I have a very simple yet annoying question. I have two view controllers, in the first one i have a button that has to go to a server and do some work, i want to show an alert view containing a spinning indicator, that will show up as soon as the button is pressed and dismissed when the second view controller loads.
I tried this way :
- (IBAction)logMeInFunction:(id)sender {
UIAlertView *waitAlert = [[UIAlertView alloc] initWithTitle:#"Please Wait...." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[waitAlert show];
/* Do Some Api testing and stuff */
[waitAlert dismissWithClickedButtonIndex:0 animated:YES];
[self performSegueWithIdentifier: #"logMe" sender: self];
}
Using this way, the alert show's up and dismisses instantly in the second view controller, not show's up in the first to inform the user that some work is being done and disappears in the second.
Any ideas?
The main problem with your code is that your server call needs to be asynchronous. You are now using dataWithContentsOfURL which is synchronous and which blocks your main thread.
An simple option would be to make that call in a other thread.
UIAlertView *waitAlert = [[UIAlertView alloc] initWithTitle:#"Please Wait...." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[waitAlert show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *strURL2 = [NSString stringWithFormat:#"MyURL"];
NSData *dataURL2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL2]];
NSString *strResult2 = [[NSString alloc] initWithData:dataURL2 encoding:NSUTF8StringEncoding];
NSDictionary *json2 = [strResult2 JSONValue];
NSMutableArray *userExists = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
[waitAlert dismissWithClickedButtonIndex:0 animated:YES];
[self performSegueWithIdentifier: #"logMe" sender: self];
});
});
Instead of showing UIAlertview, show the Custome UIView with the label and indicator...
try the below code...
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0,0.0,200.0,200.0)];
customView.layer.cornerRadius = 5.0;
customView.layer.borderColor = [UIColor lightGrayColor].CGColor;
customView.layer.borderWidth = 1.0;
[self.view addSubview:customView];
UILabel* loadingLabel = [[UILabel alloc] initWithFrame:CGRectMake(leftMargin, topMargin, 200.0, 100.0)];
[loadingLabel setNumberOfLines:4];
[loadingLabel setTextAlignment:NSTextAlignmentCenter];
[loadingLabel setText:#"Loading..."];
[customView addSubview:loadingLabel];
UIActivityIndicatorView* loadingIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(leftMargin + 75.0, topMargin + 45.0, 50.0, 50.0)];
[loadingIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
[customView addSubview:loadingIndicator];
once the server work finished hide/remove the custom view and load your next view controller....
IMO, you need to utilize background thread. The main thread will run a HUD and the background thread will hit server and perform operation.
Psuedo Code:
-(void)sendRequestToServer
{
// TODO: Call server to do some function
// Once you are done with server action, you have to come back to MAIN THREAD
[self performSelectorOnMainThread:#selector(hideHUD) withObject:nil waitUntilDone:NO];
}
- (IBAction)logMeInFunction:(id)sender
{
[MBProgresssHUD showHUDAddedTo:self.view animated:YES];
[self performSelectorInBackground:#selector(sendRequestToServer) withObject:nil];
}
-(void)hideHUD
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
NOTE:
Please look at the examples given in the clink and also have a look at dispatch_async and dispatch_sync blocks.
Hope it helps!
To possibilities:
1) UIAlertView or any other view with animation will take some mili second time to present itself on screen. So it might be possible that your service call is quick here and gives you response back before even UIAlertView present on screen.
2) UIAlertView needs a time to display on screen, so might be possible that your server call makes UIAlertview wait until web service execution done as both are performing on main thread. So you should wither use delay to call service function after displaying alert view or you should use GCD.
I have below code where I am loading some link in webview.
- (void)viewDidLoad
{
mySpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self openWebPage:fileName];
[self.view addSubview:myWebView];
[self.view addSubview:mySpinner];
mySpinner.center = CGPointMake(self.view.frame.size.width / 2.0, 100);
}
-(void)openWebPage:(NSString*)address {
NSURL*url=[NSURL URLWithString:address];
NSURLRequest*request=[NSURLRequest requestWithURL:url];
myWebView.scalesPageToFit = NO;
[myWebView loadRequest:request];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error!!!" message:#"Please make sure you are connected to 3G or Wi-Fi." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[errorView show];
mySpinner.hidden = YES;
[mySpinner stopAnimating];
}
-(void)webViewDidStartLoad:(UIWebView *)webView2 {
NSLog(#"webViewDidStartLoad");
mySpinner.hidden = NO;
[mySpinner startAnimating];
NSLog(#"step 1");
NSDate *future = [NSDate dateWithTimeIntervalSinceNow: 2 ];
[NSThread sleepUntilDate:future];
NSLog(#"step 2");
}
-(void)webViewDidFinishLoad:(UIWebView *)webView2 {
NSLog(#"webViewDidFinishLoad");
mySpinner.hidden = YES;
[mySpinner stopAnimating];
}
What I am doing is at webViewDidStartLoad I am displaying spinner and starting animating using [mySpinner startAnimating];, but it didn't spin. It just stays as it is (no spinning).
Any idea what is going wrong?
Edit 1
I have webview delegate #interface WebDetailsViewController : UIViewController<UIWebViewDelegate>
Also I have added [NSThread sleepUntilDate:future]; just to verify whether activity indicator view is animating or not.
Below is what I have from NSLog
2013-06-23 16:29:28.843 GulfLab[2048:907] webViewDidStartLoad
2013-06-23 16:29:28.845 GulfLab[2048:907] step 1
2013-06-23 16:29:30.847 GulfLab[2048:907] step 2
2013-06-23 16:29:31.836 GulfLab[2048:907] webViewDidFinishLoad
Edit 2
Well well well the problem is in below line...
[UIView beginAnimations: #"Showinfo"context: nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController: secondView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
Below is what I have...
One first view controller, I have buttons and when I click those button I am coming to second view controller and there I am displaying the web file based on button pressed.
My client wanted some effect while coming to second view and for that I added above code. But because of that, I am facing the activity problem.
Any idea what changes do I need to do?
On further investigation I found that problem is in this line...
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
but I need this line as else animation is not happening...
Also today I noticed that if I touch webview, then it starts animating
Grrr... someone help me
If somebody runs into a similar problem where there Indicator is not spinning:
Make sure the Animating property is set in your storyboard.
It's possible you aren't getting it to animate because you are not on the main UI thread. Try adding it to a dispatch queue on the main loop to force it on the main loop and it should animate.
dispatch_async(dispatch_get_main_queue(), ^{
[mySpinner startAnimating];
}];
Try to put this code after alloc init
mySpinner.hidesWhenStopped = false;
Delete mySpinner.hidden = YES/NO in your code;
Just add [mySpinner setHidesWhenStopped:YES]; in viewDidload
Then you just start and stop.
- (void)viewDidLoad
{
mySpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[mySpinner setHidesWhenStopped:YES];//set Hidden here
[self openWebPage:fileName];
[self.view addSubview:myWebView];
[self.view addSubview:mySpinner];
[self.view bringSubviewToFront:mySpinner]; //Add if still not show
}
I really think the [NSThread sleepUntilDate:future]; call is killing you in this situation. Most of my code is integration with a 3rd party piece of hardware that will block the main thread in certain scenarios and when that happens I'll have similar behaviors as yours where an element of the UI appears but is in a slightly frozen state until I either tap the screen to "jump start" the UI or the blocking call is completed.
If I were you I would start by getting rid of the sleepUntilDate call then remove the call to stop mySpinner to make sure it is in fact spinning so that when you run into a longer call of webViewDidFinishLoad you'll be assured it's working.
This is how I do a pop-up with a spinner in my code. Not exactly what you had asked but it is somewhat similar.
In your *.h file:
#interface v3ViewController : UIViewController
{
UIAlertView *megaAlert;
}
#property (nonatomic, retain) UIAlertView *megaAlert;
- (IBAction) invokeMegaAnnoyingPopup;
- (IBAction) dismissMegaAnnoyingPopup;
In your *.m file:
#synthesize megaAlert;
- (IBAction) dismissMegaAnnoyingPopup
{
[self.megaAlert dismissWithClickedButtonIndex:0 animated:YES];
self.megaAlert = nil;
}
- (IBAction) invokeMegaAnnoyingPopup
{
self.megaAlert = [[UIAlertView alloc] initWithTitle:#"Please wait..."
message:nil delegate:self cancelButtonTitle:nil
otherButtonTitles: nil];
[self.megaAlert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.center = CGPointMake(130, 70);
[indicator startAnimating];
[self.megaAlert addSubview:indicator];
}