Build the tab-bar from a plist

Working in a team, I often find myself trying to avoid using Interface Builder (IB) as much as possible. This is because trying to resolve the inevitable conflicts that arise when two developers change the same xib can be soul-destroying ⚰️. A sea of seemingly unrelated xml changes awaits anyone who dares to attempt to manually resolve xib conflicts, making it almost impossible to understand what the other developer actually changed, never mind reason why those changes have been made.

In this article, I will look at how we can avoid using IB to configure a UITabBarController instance by moving its configuration into a simple to understand plist file.

Plist Structure

<plist version="1.0">
<array>
    <dict>
        <key>TabBarTitle</key>
        <string>HOME_TAB</string>
        <key>TabBarImage</key>
        <string>tabbar_home_icon.png</string>
        <key>RootViewController</key>
        <string>HomeViewController</string>
    </dict>
</array>
</plist>

The above plist structure is used to define the indiviudal tabs of a tab-bar. Each tab has three properties:

  • TabBarTitle
  • TabBarImage
  • RootViewController

TabBarTitle is used as the tab's title, Image is used for tab's icon and RootViewController will used to instantiate an instance of UIViewController that will become that tab's view-controller.

If two developers happen to change the above plist file, it should be trivial to see what the changes are and figure out how to resolve them.

In the below example, the above plist file is called ApplicationHierarchy.

Building the tabs

-(void)createApplicationHierarchy {
    
    NSString *applicationHierarchyPlistFilePath = [[NSBundle mainBundle] pathForResource:@"ApplicationHierarchy" ofType:@"plist"];
    
    if (applicationHierarchyPlistFilePath != nil) {
    
        NSArray *applicationHierarchy = [[NSArray arrayWithContentsOfFile:applicationHierarchyPlistFilePath] retain];
        
        NSMutableArray *navigationElements = [[[NSMutableArray alloc]init] autorelease];
        
        for (int x = 0; x < [applicationHierarchy count]; x++) {
            
            NSDictionary *tabbarDictionary = [applicationHierarchy objectAtIndex:x];
            
            //Create tabbar's element's root viewcontroller
            Class rootViewControllerClass = NSClassFromString ([tabbarDictionary objectForKey:@"RootViewController"]);
            id rootViewController = [[rootViewControllerClass alloc]init];
            UINavigationController *tabRootViewNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
            [rootViewController release];
            
            //Give tab bar element a title
            NSString *tabBarTitle = NSLocalizedString([tabbarDictionary objectForKey:@"TabBarTitle"], @"");
                        
            //Give tab bar element an image
            UIImage *tabBarImage = [UIImage imageNamed:[tabbarDictionary objectForKey:@"TabBarImage"]];
            
            //create tabbar item and pass it configuration defined aboce
            UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:tabBarTitle image:tabBarImage tag:x];
            tabRootViewNavigationController.tabBarItem = tabBarItem;
                        [tabBarItem release];
            [navigationElements addObject:tabRootViewNavigationController];
            [tabRootViewNavigationController release];
            
        }
        
        [applicationHierarchy release];
        
        self.tabBarController.viewControllers = [NSArray arrayWithArray:navigationElements];
    }
    
}

In the above method, we load the contents of the ApplicationHierarchy plist into an array, iterate through that array instantiating UIViewController instances and configuring UITabBarItem instances based in the extracted plist values. Once all tabs have been configured, we then assign the view-controllers to be the tab-bar's view-controllers.