views:

150

answers:

1

Hi, I have a UITabbBarController with a UITableView. Under certain circumstances the TabBarControllers dataset requires updating when a user arrives from another view,

e.g. the initial load when the TabBarController is called the first time, or when the settings are changed.

This dataset update takes about 2 seconds and I want to show an UIActivityIndicatorView.

Trouble is that when I enter from another view I don't know which view to attach it to, since the loading of the tabbarController is carried out in the viewWillAppear method.

Any clues how I can go about this?

+1  A: 

I've done this sort of thing in the viewDidAppear method. My code kicks off a background task to load the data from a url. It also hands the background task a selector of a method to call on the controller when it is done. That way the controller is notified that the data has been downloaded and can refresh.

I don't know if this is the best way to do this, but so far it's working fine for me :-)

To give some more details, in addition to the selector of the method to call when the background task has loaded the data, I also and it a selector of a method on the controller which does the loading. That way the background task manages whats going on, but the view controller provides the data specific code.

Here's there viewDidAppear code:

- (void) viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    if (reloadData) {
        BackgroundTask *task = [[BackgroundTask alloc] initWithMethod:@selector(loadData) onObject:self];
        task.superView = self.view.superview;
        task.notifyWhenFinishedMethod = @selector(loadFinished);
        [task start];
        [task release];
    }
}

The background task has an optional superView because it will add a new UIView to it containing an activity indicator.

BackgroundTask.m looks like this:

@implementation BackgroundTask

@synthesize superView;
@synthesize longRunningMethod;
@synthesize notifyWhenFinishedMethod;
@synthesize obj;

- (BackgroundTask *) initWithMethod:(SEL)aLongRunningMethod onObject:(id)aObj {
    self = [super init];
    if (self != nil) {
        self.longRunningMethod = aLongRunningMethod;
        self.obj = aObj;
    }
    return self;
}

- (void) start {
    // Fire into the background.
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(execute:)object:nil];
    thread.name = @"BackgroundTask thread";
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskFinished:) name:NSThreadWillExitNotification object:thread];
    [thread start];
    [thread release];
}

- (void) execute:(id)anObject {

    // New thread = new pool.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    if (self.superView != nil) {
        busyIndicatorView = [[BusyIndicator alloc] initWithSuperview:self.superView];
        [busyIndicatorView performSelectorOnMainThread:@selector(addToSuperView)withObject:nil waitUntilDone:YES];
    }

    // Do the work on this thread.
    [self.obj performSelector:self.longRunningMethod];

    if (self.superView != nil) {
        [busyIndicatorView performSelectorOnMainThread:@selector(removeFromSuperView)withObject:nil waitUntilDone:YES];
    }

    [pool release];

}

- (void) taskFinished:(NSNotification *)notification {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:notification.object];
    [self performSelectorOnMainThread:@selector(notifyObject)withObject:nil waitUntilDone:NO];
}

- (void) notifyObject {
    // Tell the main thread we are done.
    if (self.notifyWhenFinishedMethod != nil) {
        [self.obj performSelectorOnMainThread:self.notifyWhenFinishedMethod withObject:nil waitUntilDone:NO];
    }
}

- (void) dealloc {
    self.notifyWhenFinishedMethod = nil;
    self.superView = nil;
    self.longRunningMethod = nil;
    self.obj = nil;
    [super dealloc];
}

@end

Finally as I said I put up a activity indicator. I have a xib which contains a 50% transparent blue background with an activity indicator in the middle. There is a controller for it which has this code:

@implementation BusyIndicator

@synthesize superView;
@synthesize busy;


- (BusyIndicator *) initWithSuperview:(UIView *)aSuperView {
    self = [super initWithNibName:@"BusyIndicator" bundle:nil];
    if (self != nil) {
        self.superView = aSuperView;
    }
    return self;
}

- (void) addToSuperView {

    // Adjust view size to match the superview.
    [self.superView addSubview:self.view];
    self.view.frame = CGRectMake(0,0, self.superView.frame.size.width, self.superView.frame.size.height);

    //Set position of the indicator to the middle of the screen.
    int top = (int)(self.view.frame.size.height - self.busy.frame.size.height) / 2;
self.busy.frame = CGRectMake(self.busy.frame.origin.x, top, self.busy.frame.size.width, self.busy.frame.size.height);

    [self.busy startAnimating];
}

- (void) removeFromSuperView {
    [self.busy stopAnimating];
    [self.view removeFromSuperview];
}

- (void) dealloc {
    self.superView = nil;
    [super dealloc];
}

@end

Hoep this helps.

Derek Clarkson
Hi Derek, still playing around with your solution, but must be able to implement in the viewWillAppear method since I must load the tabledata before [super viewWillAppear] is executed. Will get back when I am trough my testing.Thanks in advance, iFloh
iFloh