My UIActivityIndicatorView disappears too soon making it useless, would delay be the best way to fix this?

Need a loader and spinner to show on my pages that display a list of people on my parse.com database. Right now I'm concentrating on one controller my UITableViewController.

Here is my code:

- (void)viewDidLoad
{
    [super viewDidLoad];
    dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
    UIView *taggedView = [[LoadingView alloc] initWithFrame:[[self view] bounds]];
    [taggedView setTag:17];
    [[self view] addSubview:taggedView];

    dispatch_async(downloadQueue, ^{
        //Load my data from parse.com here into an NSMutableArray
        //which is the used in my datasource methods
        NSLog(@"downloading");


        dispatch_async(dispatch_get_main_queue(), ^{
            //Here is where I hide the loader view
            NSLog(@"downloading done.");
            UIView *viewToRemove = [[self view] viewWithTag:17];
            [viewToRemove removeFromSuperview];
        });
    });

I've taken out the code that loads my data from parse.com to make things cleaner but the commeting makes it clear where it all goes.

I never see the loader unless I comment out the viewToRemove lines. Then it just obviously stays on the screen. The problem is it just loads and is taken away too quickly. Also for some reason I can also see the table row lines and I do not wish to see them. I want the loader view view to cover the whole area until data is loaded then maybe a second or 2 later remove itself.

LoaderView file (found this on the web):

#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;
}

So my mission is to have a white backgrounded view with a spinner and word "Loading". Is my method of achieving this efficient or is there a better way to do this? If so I'd appreciate some help here.

Kind regards

UPDATE

Parse code that gets the data for me.

// Grab data for datasource and store in people array
 NSLog(@"view will appear");
people = [[NSMutableArray alloc] init];

PFQuery *query = [PFQuery queryWithClassName:@"People"];
[query whereKey:@"active" equalTo:@1];
[query orderByDescending:@"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

    if (!error) {
        for (PFObject *object in objects) {
          //  NSLog(@"%@", object);
            Person *person = [[Person alloc] init];
            [person setName:[object objectForKey:@"name"]];
            [person setNotes:[object objectForKey:@"notes"]];
            [person setAge:[[object objectForKey:@"age"] intValue]];
            [person setSince:[object objectForKey:@"since"]];
            [person setFrom:[object objectForKey:@"from"]];
            [person setReferenceNumber:@"14-334544"];

            PFFile *userImageFile = object[@"image"];
            [userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
                if (!error) {
                    UIImage *image = [UIImage imageWithData:imageData];
                    [person setImage:image];
                }
            }];


            [person setActive:[[object objectForKey:@"active"] intValue]];
            [person setObjectId:[object objectId]];
            [people addObject:person];


        }


        dispatch_async(dispatch_get_main_queue(), ^ {
            [[self tableView] reloadData];
        });


    } else {
        // Log details of the failure
        NSLog(@"Error: %@ %@", error, [error userInfo]);


    }


}];

Answers


All of your Parse code is asynchronous, so it finishes nearly immediately. By the time the view actually appears (after viewDidLoad, viewWillAppear, viewWillLayoutSubviews, and viewDidLayoutSubviews all finish), all your Parse code is done since it returns immediately.

For the effect you're looking for, you should block your background Parse thread.

For example, change:

[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
    if (!error) {
        UIImage *image = [UIImage imageWithData:imageData];
        [person setImage:image];
    }
}];

to:

NSError *error;
NSData *imageData = [userImageFile getData:&error];
if (!error) {
    UIImage *image = [UIImage imageWithData:imageData];
    [person setImage:image];
}

You'll need to similarly change [PFQuery -findObjectsInBackgroundWithBlock:] to use the synchronous version.

However, consider the alternative: don't show a loader, keep using the background blocks, and then display data as it comes in. This is a much nicer experience than showing a loader.

tl;dr: you're using both GCD async blocks AND Parse's background blocks. Just use one or the other.


Since you're using findObjectsInBackgroundWithBlock the code that is to be executed on the main thread is executed immediately, and the block is executed when the objects are found. Your code to remove the loader should be executed within the dispatch async that's already in that block:

[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    // do something with the objects
    dispatch_async(dispatch_get_main_queue(), ^{
            [[self tableView] reloadData];
            // remove the loader
            NSLog(@"downloading done.");
            UIView *viewToRemove = [[self view] viewWithTag:17];
            [viewToRemove removeFromSuperview];
    });
}];

For the question about seeing the cells prior to the objects loading, you should make sure that you're reusing cells and that your tableViewController's delegate methods are responding to the number of objects appropriately, so that before you have downloading the objects you are returning no data to be placed into the tableview.


Need Your Help

Debugging crash in Cordova App

android angularjs cordova bluetooth

I am currently developing a cordova / AngularJS based cross platform app with some third party plugins (mainly Bluetooth) and am looking for a way to debug the native Java code of the app or log cr...

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.