I am having some trouble getting my UIActivityIndicatorView to start animating. Here is my setup:
In my viewDidLoad in my view controller I have:
- (void)viewDidLoad{
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
[super viewDidLoad];
}
The selector getSchoolList communicates with a server to retrieve a list of schools in a given state. Then, the selector updateUI is called to populate my UIPickerView with the list. In my updateUI selector I have:
-(void)updateUI {
_schools = [_server returnData];
if(!(_schools == nil)) {
NSLog(#"update the UI");
}
else
NSLog(#"Error:Show re-load button");
[_activityIndicator stopAnimating];
}
When I run this code, my UIActivityIndicatorView shows up, but does not animate. Can someone explain the proper way to animate my UIActivityIndicatorView? Any help is much appreciated.
You need to add the UIActivityIndicatorView to your view in viewDidLoad like this:
- (void)viewDidLoad {
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self addSubview:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
[super viewDidLoad];
}
EDIT
If _activityIndicator is a properly connected IBOutlet to a UIActivityIndicatorView, you should only need to check the 'animating' box. There would be no need to alloc/init another UIActivityIndicatorView.
Breakpoint the update function, but I don't see where you add that as a view to the hierarchy. I think you're looking at a different indicator view in the program.
Related
I am adding activity indicator on top of the view and wish to disable the selections in the background when the activity indicator is on. Also for some reason, my activity indicator is still spins for about 30-45 seconds(depending on the network speed) after the data is displayed on the table view. I have created a category for activity indicator.
Activity Indicator category code:
- (UIView *)overlayView {
return objc_getAssociatedObject(self, OverlayViewKey);
}
- (void)setOverlayView:(UIView *)overlayView {
objc_setAssociatedObject(self, OverlayViewKey, overlayView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)showActivityIndicatorForView:(UIView *)view {
self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
self.center = self.overlayView.center;
[view setUserInteractionEnabled:NO];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self.overlayView setUserInteractionEnabled:NO];
[self startAnimating];
[self.overlayView addSubview:self];
[view addSubview:self.overlayView];
[view bringSubviewToFront:self.overlayView];
self.hidesWhenStopped = YES;
self.hidden = NO;
}
- (void)hideActivityIndicatorForView:(UIView *)view {
[self stopAnimating];
[self.overlayView setUserInteractionEnabled:YES];
[self.overlayView removeFromSuperview];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
[view setUserInteractionEnabled:YES];
}
Usages in table view controller:
#interface MyTableViewController()
#property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
#end
#implementation MyTableViewController
- (id) initWithSomething:(NSString *)something {
self = [super init];
if (self) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.overlayView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self getDataServiceRequest];
[self.activityIndicator showActivityIndicatorForView:self.navigationController.view];
}
- (void)requestCompletionCallBack sender:(ServiceAPI *)sender {
// Do something here with the data
[self.activityIndicator hideActivityIndicatorForView:self.navigationController.view];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
#end
What am I doing wrong here? Why am I still able to select the data in the background when the activity indicator is on and even after disabling the user interaction.
Move your call to hideActivityIndicatorForView to inside the call to dispatch_async(dispatch_get_main_queue(). It's a UI call, and needs to be done on the main thread.
As for how to disable other actions on your view controller, you have a few options. One simple thing I've done is the put the activity indicator inside a view that's pinned to the whole screen, set to opaque=false, and with a color that's black with an alpha setting of 0.5. That way the content underneath is visible but the user can't click on it. You need to add an outlet to your "coveringView" and show-hide it instead of showing/hiding the activity indicator view.
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
fix it
[self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
I am trying to create methods that I can reuse to start and stop spinning wheels during different lengthy activities such as syncing a table with a server.
My start method:
-(void) startSpinner {
UIActivityIndicatorView *activityIndicator;
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
activityIndicator.center = self.view.center;
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
}
and end method
-(void)endSpinner:(UIActivityIndicatorView *) spinner forTable:(UITableView *)tableView {
[tableView reloadData];
[spinner stopAnimating];
tableView.userInteractionEnabled = YES;
}
The problem I'm running into is the end method does not recognize the uiactivityidicator created in the start method.
Should I be saving this in a property? Or how can I grab the spinner from a different method in order to save it.
I'd like to set this up in reusable code as I have many tableviews in different view controllers where I would like to include this code. The tableviews already have properties but do I have to set up a uiactivityindicator property in every view controller where I want to include a spinner?
Thanks for any insights.
Declare a property in AppDelegate:
#property (strong,nonatomic) MyActivityIndicator *activity;
and initialise it:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_activity=[[MyActivityIndicator alloc]init];
}
and then declare the following methods in appDelegate:
- (void)showActivity
{
dispatch_async(dispatch_get_main_queue(), ^{
[_window addSubview:_activity];
[_activity startAnimating];
});
}
- (void)hideActivity
{
dispatch_async(dispatch_get_main_queue(), ^{
//also remove activity from window
[_activity stopAnimating];
});
}
you can call these two methods from any class:
[(AppDelegate*)[UIApplication sharedApplication].delegate showActivity];
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 have an app with a table view controller in which a user selects a US state, a web service is called and data is displayed for that state in the destination table view controller. Since the web service can take some time to complete I want an activity indicator. Since there will be no temporary data to display, I need this to be processed synchronously. So my task is pretty simple: start the activity indicator, call the web service, and after it completes, stop the activity indicator.
I am obviously doing something wrong and no activity indicator ever displays.
Here is the code from my destination table view controller's viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.tableView bringSubviewToFront:spinner];
spinner.hidesWhenStopped = YES;
spinner.hidden = NO;
[spinner startAnimating];
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
[self.tableView reloadData];
[spinner stopAnimating];
}
Header:
#property (strong, nonatomic) UIActivityIndicatorView *spinner;
GaugeList is the object which makes the web service call.
Can someone tell me how to get an activity indicator view to appear? Thanks!
You forgot to add spinner on table view. Your code should look as follows:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spiner.center = //set some center
[self.tableView addSubview: spinner];
[self.tableView bringSubviewToFront:spinner];
spinner.hidesWhenStopped = YES;
spinner.hidden = NO;
[spinner startAnimating];
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
[self.tableView reloadData];
[spinner stopAnimating];
}
Also you send requests to a web service in main thread. This is bad practice. I would suggest something like following:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spiner.center = //set some center
[self.tableView addSubview: spinner];
[self.tableView bringSubviewToFront:spinner];
spinner.hidesWhenStopped = YES;
spinner.hidden = NO;
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[spinner stopAnimating];
});
});
}
At first you should add activity indicator to some view to show it. But you can not add it to UITableView, because UITableView is subclass of UIScrollView and you will see floating activity indicator. The best way in your case is to add activity indicator to navigation bar, etc. Or if you want to disable table view you should write something like this:
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
UIView *dummyView = [[UIView alloc] init];
dummyView.frame = self.tableView.bounds;
dummyView.alpha = 0.5f;
dummyView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
dummyView.userInteractionEnabled = YES;
dummyView.backgroundColor = [UIColor blackColor];
[dummyView addSubview:activityIndicator];
activityIndicator.center = dummyView.center;
[self.tableView addSubview:dummyView];
}
Try using self.spinner instead of using spinner.
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];
}