IOS: ActivityIndicator over UITableView... How to?

26,265

Solution 1

You need to add the UIActivityIndicatorView to something. You can add it to a UITableView header view. To do this you will need to provide your own custom view.

...
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
[view addSubview:ac]; // <-- Your UIActivityIndicatorView
self.tableView.tableHeaderView = view;
...

Solution 2

I have been trying to find a solution for that and have read a lot of forums, but i did not get what I wanted. After understanding how the activity indicator and table view controller works I came up with the following solution.

For some reason if you try to start the activity indicator on the same thread with the tableReload or any other expensive process, the activity indicator never runs. If you try to run table reload or some other operation in another thread then it might not be safe and might produce error or unwanted results. So we can run the method that presents the activity indicator on another thread.

I have also combined this solution with the MBProgressHUD to present something that look nicer that a view with an activity indicator. In any case the looks of the activityView can be customized.

-(void)showActivityViewer
{
    AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    UIWindow *window = delegate.window;
    activityView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, window.bounds.size.width, window.bounds.size.height)];
    activityView.backgroundColor = [UIColor blackColor];
    activityView.alpha = 0.5;

    UIActivityIndicatorView *activityWheel = [[UIActivityIndicatorView alloc] initWithFrame: CGRectMake(window.bounds.size.width / 2 - 12, window.bounds.size.height / 2 - 12, 24, 24)];
    activityWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite;
    activityWheel.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
                                      UIViewAutoresizingFlexibleRightMargin |
                                      UIViewAutoresizingFlexibleTopMargin |
                                      UIViewAutoresizingFlexibleBottomMargin);
    [activityView addSubview:activityWheel];
    [window addSubview: activityView];

    [[[activityView subviews] objectAtIndex:0] startAnimating];
}

-(void)hideActivityViewer
{
    [[[activityView subviews] objectAtIndex:0] stopAnimating];
    [activityView removeFromSuperview];
    activityView = nil;
}

- (IBAction)reloadDataAction:(id)sender {
    [NSThread detachNewThreadSelector:@selector(showActivityViewer) toTarget:self withObject:nil];  
    //... do your reload or expensive operations
    [self hideActivityViewer];
}

Solution 3

[iOS 5 +]
If you just want to show the activityWheel without an additional parentview, you can also add the activityWheel straight to the tableView and calculate the y value for the frame using the tableViews contentOffset:

@interface MyTableViewController ()
@property (nonatomic, strong) UIActivityIndicatorView *activityView;
@end

// ....

- (void) showActivityView {
    if (self.activityView==nil) {
        self.activityView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero];
        [self.tableView addSubview:self.activityView];
        self.activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
        self.activityView.hidesWhenStopped = YES;
        // additional setup...
        // self.activityView.color = [UIColor redColor]; 
    }
    // Center 
    CGFloat x = UIScreen.mainScreen.applicationFrame.size.width/2;
    CGFloat y = UIScreen.mainScreen.applicationFrame.size.height/2;
    // Offset. If tableView has been scrolled
    CGFloat yOffset = self.tableView.contentOffset.y;
    self.activityView.frame = CGRectMake(x, y + yOffset, 0, 0);

    self.activityView.hidden = NO;
    [self.activityView startAnimating];
}

- (void) hideActivityView {
    [self.activityView stopAnimating];
}

If the activityView doesn't show up immediately (or never), refer to zirinisp's answer or do the heavy work in the background.

Solution 4

There's workaround for UIViewController. (You can use a UIViewController with a table view to achieve a UITableViewController.) In storyboard, add a activityIndicator in the view of UIViewController. Then in viewDidLoad, add following:

[self.activityIndicator layer].zPosition = 1;

The activityIndicator will be displayed over the table view.

Share:
26,265

Related videos on Youtube

Tom
Author by

Tom

Updated on July 25, 2022

Comments

  • Tom
    Tom almost 2 years

    i want display an activity indicator over a uitableview while it's loading data (in another thread). So in the ViewDidLoad method of the UITableViewController:

    -(void)viewDidLoad
     {
        [super viewDidLoad];
    
        //I create the activity indicator
        UIActivityIndicatorView *ac = [[UIActivityIndicatorView alloc]  
                          initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        [ac startAnimating];
    
        //Create the queue to download data from internet
        dispatch_queue_t downloadQueue = dispatch_queue_create("PhotoDownload",NULL); 
        dispatch_async(downloadQueue, ^{
          //Download photo
          .......
          .......
          dispatch_async(dispatch_get_main_queue(), ^{
             ......
             ......
             [ac stopAnimating];
          });
        });
       .......
    

    Why the activity indicator don't display over the table view? How can i achieve it?

    • Maksim
      Maksim about 12 years
      Also interested in this question. But as far as I know UIView subclasses are not thread-safe.
  • jmstone617
    jmstone617 about 12 years
    Beat me to the punch! +1 for the answer. An activity indicator is a UIView, so it needs to be added to the view hierarchy somehow!
  • dfdumaresq
    dfdumaresq almost 11 years
    This worked quite well for me, after several alternatives. Thanks @zirinisp!
  • cookiemonsta
    cookiemonsta over 10 years
    been searching for a quite a while now and i've got to say this is the best solution so far.
  • aherrick
    aherrick about 10 years
    This works but how can I move this into a helper class so that I can call on any view?
  • zirinisp
    zirinisp about 10 years
    Create a category with the above methods inside it
  • Peter N Lewis
    Peter N Lewis about 10 years
    This seems extremely hazardous. UI manipulation must happen on the main thread (that is adding and removing the subview at the very least). Also, given showActivityViewer and hideActivityViewer are operating on two different threads, any number of race conditions could happen (eg hideActivityViewer could be called before window addSubview and all hell breaking loose).
  • zirinisp
    zirinisp about 10 years
    @PeterNLewis What do you propose to improve this solution.
  • Peter N Lewis
    Peter N Lewis about 10 years
    You need to perform the expensive operation on a background thread, and from there perform any UI actions (and perhaps any model related actions as well) on the main thread. Eg, show the UIActivityIndicatorView, block any UI, detachNewThreadSelector to perform the expensive operation, and return. At the end of expensive operation, perform on the main thread the UI removal of the UI block and the UIActivityIndicatorView.
  • sports
    sports about 9 years
    and where is ac living before adding it to the newly created view?