22 April 2012

UITableView Activity Indicator the Apple way

Please note that this code should run on iOS5. I'm not sure about previous version as they don't have ARC
 Note! There is a code fix available now, that provides orientation and resizing stability: https://github.com/Uko/UILoadingView 

While developing iOS apps sometimes you'll need to load some data from the Network and you put up some activity indicator and etc… But once I've encountered a difficulty trying to implement UITableView Activity Indicator the Apple way, like the one in YouTube, AppStore, iTunes:


You can make a view like this one:

LoadingView.h:
#import <UIKit/UIKit.h>

@interface LoadingView : UIView

@end


LoadingView.m:
#import "LoadingView.h"

@implementation LoadingView

#define LABEL_WIDTH 80
#define LABEL_HEIGHT 20

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor whiteColor]];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake((self.bounds.size.width-LABEL_WIDTH)/2+20,
(self.bounds.size.height-LABEL_HEIGHT)/2,
LABEL_WIDTH,
LABEL_HEIGHT)];
label.text = @"Loading…";
label.center = self.center;
UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame = CGRectMake(label.frame.origin.x - LABEL_HEIGHT - 5,
label.frame.origin.y,
LABEL_HEIGHT,
LABEL_HEIGHT);
[spinner startAnimating];
[self addSubview: spinner];
[self addSubview: label];
}
return self;
}

@end


So, we make a UIView subclass and overload initWithFrame: so it put's a "Loading…" label and a spinning activity indicator next to it, right in the middle of the frame.

Also your loading code should look like this:
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
[self.view addSubview:[[LoadingView alloc] initWithFrame:self.view.bounds]];
dispatch_async(downloadQueue, ^{
//do your downloading
dispatch_async(dispatch_get_main_queue(), ^{
//maybe some UI stuff
[[self.view.subviews lastObject] removeFromSuperview];
});
});
dispatch_release(downloadQueue);


Here we make a new queue and before passing a download block to it we add our LoadingView to the top of controller's main view and init it with frame of the main view's frame, so it covers everything by itself. Then we load our data and usually you want to show it in your view as fast as you can, but you should run it in the main queue. And after reloading your data visually, you remove last view from the main view's subviews (the last one will be our LoadingView as we've added it just before running the download). Don't forget to release your download queue after you're done with it.

I'm new to  iOS development, so this code isn't as good as I wish. I't still rotting badly and uh… you'll probably put loading in the viewWillAppear: as then main view will have correct bounds (including displacement for navigation bar and tab bar). But I've spent a lot of time to find out how this loading screen can be achieved, so I've written this blogpost to help people that'll be looking for the same stuff. Also feel free to give your own suggestions in comments and help to make this tutorial better.

 Note! There is a code fix available now, that provides orientation and resizing stability: https://github.com/Uko/UILoadingView 

3 comments:

  1. )) Почитай про UIProgressView )
    Воно з коробки має схожий функціонал )

    ReplyDelete
    Replies
    1. Стоп, хіба UIProgressView не є просто баром, який заповнюється від 0 до 1?

      Delete
    2. Ти з UIProgressBar плутаєш

      Delete