Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make UITabBarController load view controllers lazily?

Tags:

iphone

I have a UITabBarController created programaticaly that manages 4 subclasses of UIViewController. Something like:

//Create Controller 1
    self.terminal = [[[TerminalController alloc] initWithNibName:@"TerminalView" bundle:nil] autorelease];
    UINavigationController* navTerminal = [[[UINavigationController alloc] initWithRootViewController:terminal] autorelease];
    navTerminal.title = __(@"Terminal");
    navTerminal.navigationBar.barStyle = UIBarStyleBlackOpaque;
    navTerminal.tabBarItem.image = [UIImage imageNamed:@"tab_terminal.png"];        

    //Create Controller 2
    self.history = [[[HistoryController alloc] initWithNibName:@"HistoryView" bundle:nil] autorelease];
    UINavigationController* navHistory =  [[[UINavigationController alloc] initWithRootViewController:history] autorelease];
    navHistory.title = __(@"History");
    navHistory.navigationBar.barStyle = UIBarStyleBlackOpaque;
    navHistory.tabBarItem.image = [UIImage imageNamed:@"tab_history.png"];

    //Create Controller 3
    self.settings = [[[SettingsController alloc] initWithNibName:@"SettingsView" bundle:nil] autorelease];
    UINavigationController* navSettings =  [[[UINavigationController alloc] initWithRootViewController:settings] autorelease];
    navSettings.title = __(@"Settings");
    navSettings.navigationBar.barStyle = UIBarStyleBlackOpaque;
    navSettings.tabBarItem.image = [UIImage imageNamed:@"tab_settings.png"];

    //Create Controller 4
    HelpController* help = [[[HelpController alloc] initWithNibName:@"HelpView" bundle:nil] autorelease];
    UINavigationController* navHelp =  [[[UINavigationController alloc] initWithRootViewController:help] autorelease];
    navHelp.title = __(@"Help");
    navHelp.navigationBar.barStyle = UIBarStyleBlackOpaque;
    navHelp.tabBarItem.image = [UIImage imageNamed:@"tab_help.png"];

    //Create Tab Bar an add it's view to window.
    self.tabBar = [[[UITabBarController alloc] initWithNibName:nil bundle:nil] autorelease];
    tabBar.viewControllers = [[[NSArray alloc] initWithObjects:navTerminal, navHistory, navSettings, navHelp, nil] autorelease];
    tabBar.delegate = self;     

    [window addSubview:tabBar.view];

Is there a way to tell the UITabBarController to load the view controllers lazily? ej, when the user clicks one of the tab bar items or when tabBarController setSelectedIndex is called?

like image 399
Rafael Vega Avatar asked Jul 28 '09 22:07

Rafael Vega


3 Answers

I'm doing this very thing in one of my apps. The trick is to make your view controller NOT be a subclass of UITabBarController, but rather UIViewController, and implement UITabBarDelegate. I created the view for this in IB, laying out the tab bar (with the proper # of buttons, images, tags, etc.) and a placeholder UIView which is used to properly place the subviews that are swapped in and out. View switching happens on tabBar:didSelectItem: It looks something like this:

// MyTabBarController.h
@class MyFirstViewController;
@class MySecondViewController;
@class MyThirdViewController;

@interface MyTabBarController : UIViewController <UITabBarDelegate> {
    IBOutlet UIView *placeholderView;
    IBOutlet UITabBar *tabBar;
    MyFirstViewController *firstViewController;
    MySecondViewController *secondViewController;
    MyThirdViewController *thirdViewController;
    UIViewController *currentViewController;
}
@property (nonatomic, retain) MyFirstViewController *firstViewController;
@property (nonatomic, retain) MySecondViewController *secondViewController;
@property (nonatomic, retain) MyThirdViewController *thirdViewController;

- (void) switchToView:(UIViewController*)aViewController;
@end


//  MyTabBarController.m
#import "MyTabBarController.h"
#import "MyFirstViewController.h"
#import "MySecondViewController.h"
#import "MyThirdViewController.h"

enum {
    kView_First = 1,
    kView_Second,
    kView_Third
};

@implementation MyTabBarController

@synthesize firstViewController, secondViewController, thirdViewController;

- (void) viewDidLoad {
    // Default to first view.
    tabBar.selectedItem = [tabBar.items objectAtIndex:0];
    MyFirstViewController *viewController = [[MyFirstViewController alloc] initWithNibName:@"FirstView" bundle:nil];
    self.firstViewController = viewController;
    [viewController release];
    [self switchToView:firstViewController];
}

- (void)viewWillAppear:(BOOL)animated {
    // Tell our subview.
    if( currentViewController != nil ) {
        [currentViewController viewWillAppear:animated];
    }
}

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
    switch (item.tag) {
        case kView_First: {
            if (firstViewController == nil) {
                MyFirstViewController *viewController = [[MyFirstViewController alloc]
                    initWithNibName:@"FirstView" bundle:nil];
                self.firstViewController = viewController;
                [viewController release];
            }

            [self switchToView:firstViewController];
        }
        break;

        case kView_Second:
            if (secondViewController == nil) {
                MySecondViewController *viewController = [[MySecondViewController alloc]
                initWithNibName:@"SecondView" bundle:nil];
                self.secondViewController = viewController;
                [viewController release];
            }

            [self switchToView:secondViewController];
            break;

        case kView_Third: {
            if (timesViewController == nil) {
                MyThirdViewController *viewController = [[MyThirdViewController alloc]
                initWithNibName:@"ThirdView" bundle:nil];
                self.thirdViewController = viewController;
                [viewController release];
            }

            [self switchToView:thirdViewController];
        }
        break;              
    }
}

- (void) switchToView:(UIViewController*)aViewController {
    if( aViewController == currentViewController ) return;

    UIView *aView= aViewController.view;                
    [aViewController viewWillAppear:NO];
    if( currentViewController != nil ) {
        [currentViewController viewWillDisappear:NO];
        [currentViewController.view removeFromSuperview];       
    }
    aView.frame = placeholderView.frame;
    [self.view insertSubview:aView aboveSubview:placeholderView];
    if( currentViewController != nil ) {
        [currentViewController viewDidDisappear:NO];
    }
    [aViewController viewDidAppear:NO];
    currentViewController = aViewController;
}
@end

The code could surely be made more DRY, but you get the idea.

like image 165
zpasternack Avatar answered Nov 05 '22 19:11

zpasternack


What are you trying to load lazily?

This is a pretty standard UITabBarController implementation. I have one very similar to it in an application I am writing. In my code the viewDidLoad (the method called after a controller has loaded its associated views into memory) is not called until the tab is touched.

I do believe the way you have coded (aside from all of the autoreleased objects) is the preferred method of creating this kind of UI.

like image 35
Lounges Avatar answered Nov 05 '22 19:11

Lounges


A UITabBarController requires that actual view controllers be set for its viewControllers property - there's no lazy loading available from Apple's frameworks. The tab bar controller relies on certain aspects of the controllers it loads for its properties. However, it doesn't call viewDidLoad until the tab is pressed for the first time.

What you can do instead is create your own "placeholder" view controller that, in its viewDidLoad or viewWillAppear methods, replaces itself in the tab bar controller with its actual view controller. That way, you can minimize the memory used by the view controllers held by the tab bar controller until you load a certain tab's view controller and replace it with your more memory- and processor-intensive controller.

Side note: You'll want to change the tab bar controller's viewControllers property directly, rather than use the setViewControllers:animated: method, so that your users don't see an animation every time you load a new controller.

like image 3
Tim Avatar answered Nov 05 '22 19:11

Tim