Search code examples
javascriptiostitaniumappceleratorappcelerator-mobile

Custom Tab Bar in Appcelerator - How to return to root window?


I've got a bug which needs fixing in my iOS app developed on Appcelerator Titanium.

I've been using customTabBar (available on GitHub) to create a bespoke tabBar and it works great!

The only small issue is that it removes the option to tab on the icons and return to the root window (like a proper native tabBar would do in iOS).

So if I drill down 3 or 4 windows in my app, tapping the tab icon does nothing, I have to navigate back to the beginning by tapping back multiple times.

Here is the full customTabBar.js script I am using:

CustomTabBar = function(settings) {
var tabBarItems = [];
var tabCurrent = 2;

var resetTabs = function() {
    for(var i = 0; i < tabBarItems.length; i++) {
        // Clear all the images to make sure only
        // one is shown as selected
        tabBarItems[i].image = tabBarItems[i].backgroundImage;
    }
};

var assignClick = function(tabItem) {
    tabItem.addEventListener('click', function(e) {
        // Just fetching the 'i' variable from the loop
        var pos = e.source.pos;

        if (tabCurrent == pos) {
            // TODO
            // Change back to root window, like the native tab action.

            // code must go in here



            return false;
        }       

        // Switch to the tab associated with the image pressed
        settings.tabBar.tabs[pos].active = true;
        tabCurrent = pos;


        // Reset all the tab images
        resetTabs();

        // Set the current tab as selected
        tabBarItems[pos].image = settings.imagePath + settings.items[pos].selected;     
    });
};

// Create the container for our tab items
var customTabBar = Ti.UI.createWindow({
    height: 48,
    backgroundImage:'images/tabbarbackground.png',
    bottom: 0
});


for(var i = 0; i < settings.items.length; i++) {
    // Go through each item and create an imageView
    tabBarItems[i] = Titanium.UI.createImageView({
        // background is the default image
        backgroundImage: settings.imagePath + settings.items[i].image,

        width: settings.width,
        height: settings.height,
        left: settings.width * i
    });

    // Pass the item number (used later for changing tabs)
    tabBarItems[i].pos = i;
    assignClick(tabBarItems[i]);

    // Add to the container window
    customTabBar.add(tabBarItems[i]);
}

// Display the container and it's items
customTabBar.open();

// Set the first item as current :)
resetTabs();
//tabBarItems[0].image = settings.imagePath + settings.items[0].selected;
tabBarItems[2].image = settings.imagePath + settings.items[2].selected;

return {
    hide: function() { customTabBar.hide(); },
    show: function() { customTabBar.show(); }
};
};

The function that contains the bit I need adding is already marked up, but is just empty. Here it is:

var assignClick = function(tabItem) {
    tabItem.addEventListener('click', function(e) {
        // Just fetching the 'i' variable from the loop
        var pos = e.source.pos;

        if (tabCurrent == pos) {
            // TODO
            // Change back to root window, like the native tab action.

            // code must go in here



            return false;
        }       

        // Switch to the tab associated with the image pressed
        settings.tabBar.tabs[pos].active = true;
        tabCurrent = pos;


        // Reset all the tab images
        resetTabs();

        // Set the current tab as selected
        tabBarItems[pos].image = settings.imagePath + settings.items[pos].selected;     
    });
};

Solution

  • customTabBar places a window (really, just a view) over the existing tab bar. Then it handles clicks that come through. But you must handle the click events to switch between tabs, and as you have noted, track all of the windows that are on the stack.

    But you know what? You're working too hard. The platform already does all that for you.

    Pass click events through (by disabling touch on the overlay), and the underlying tab group will work its own magic. Then all you need to do is update the UI with the tab group's focus event (evt.index is the focused tab, and evt.previousIndex the blurred).

    app.js:

    Ti.include('overrideTabs.js');
    
    /*
     This is a typical new project -- a tab group with three tabs.
     */
    
    var tabGroup = Ti.UI.createTabGroup();
    
    /*
     Tab 1.
     */
    var win1 = Ti.UI.createWindow({ title: 'Tab 1', backgroundColor: '#fff' });
    var tab1 = Ti.UI.createTab({
        backgroundImage: 'appicon.png',
        window: win1
    });
    var button1 = Ti.UI.createButton({
        title: 'Open Sub Window',
        width: 200, height: 40
    });
    button1.addEventListener('click', function (evt) {
        tab1.open(Ti.UI.createWindow({ title: 'Tab 1 Sub Window', backgroundColor: '#fff' }));
    });
    win1.add(button1);
    tabGroup.addTab(tab1);
    
    /*
     Tab 2.
     */
    tabGroup.addTab(Ti.UI.createTab({
        backgroundImage: 'appicon.jpg',
        window: Ti.UI.createWindow({ title: 'Tab 2', backgroundColor: '#fff' })
    }));
    
    /*
     Tab 3.
     */
    tabGroup.addTab(Ti.UI.createTab({
        backgroundImage: 'appicon.png',
        window: Ti.UI.createWindow({ title: 'Tab 3', backgroundColor: '#fff' })
    }));
    
    /*
     Now call the overrideTabs function, and we're done!
     */
    overrideTabs(
        tabGroup, // The tab group
        { backgroundColor: '#f00' }, // View parameters for the background
        { backgroundColor: '#999', color: '#000', style: 0 }, // View parameters for selected tabs 
        { backgroundColor: '#333', color: '#888', style: 0 } // View parameters for deselected tabs
    );
    
    tabGroup.open();
    

    overrideTabs.js:

        /**
     * Override a tab group's tab bar on iOS.
     *
     * NOTE: Call this function on a tabGroup AFTER you have added all of your tabs to it! We'll look at the tabs that exist
     * to generate the overriding tab bar view. If your tabs change, call this function again and we'll update the display.
     *
     * @param tabGroup The tab group to override
     * @param backgroundOptions The options for the background view; use properties like backgroundColor, or backgroundImage.
     * @param selectedOptions The options for a selected tab button.
     * @param deselectedOptions The options for a deselected tab button.
     */
    function overrideTabs(tabGroup, backgroundOptions, selectedOptions, deselectedOptions) {
        // a bunch of our options need to default to 0 for everything to position correctly; we'll do it en mass:
        deselectedOptions.top = deselectedOptions.bottom
            = selectedOptions.top = selectedOptions.bottom
            = backgroundOptions.left = backgroundOptions.right = backgroundOptions.bottom = 0;
    
        // create the overriding tab bar using the passed in background options
        backgroundOptions.height = 50;
        var background = Ti.UI.createView(backgroundOptions);
    
        // pass all touch events through to the tabs beneath our background
        background.touchEnabled = false;
    
        // create our individual tab buttons
        var increment = 100 / tabGroup.tabs.length;
        deselectedOptions.width = selectedOptions.width = increment + '%';
        for (var i = 0, l = tabGroup.tabs.length; i < l; i++) {
            var tab = tabGroup.tabs[i];
    
            // position our views over the tab.
            selectedOptions.left = deselectedOptions.left = increment * i + '%';
    
            // customize the selected and deselected based on properties in the tab.
            selectedOptions.title = deselectedOptions.title = tab.title;
            if (tab.backgroundImage) {
                selectedOptions.backgroundImage = deselectedOptions.backgroundImage = tab.backgroundImage;
            }
            if (tab.selectedBackgroundImage) {
                selectedOptions.backgroundImage = tab.selectedBackgroundImage;
            }
            if (tab.deselectedBackgroundImage) {
                deselectedOptions.backgroundImage = tab.deselectedBackgroundImage;
            }
            selectedOptions.visible = false;
            background.add(tab.deselected = Ti.UI.createButton(deselectedOptions));
            background.add(tab.selected = Ti.UI.createButton(selectedOptions));
    
    
        }
    
        // update the tab group, removing any old overrides
        if (tabGroup.overrideTabs) {
            tabGroup.remove(tabGroup.overrideTabs);
        }
        else {
            tabGroup.addEventListener('focus', overrideFocusTab);
        }
    
        tabGroup.add(background);
        tabGroup.overrideTabs = background;
    }
    
    function overrideFocusTab(evt) {
        if (evt.previousIndex >= 0) {
            evt.source.tabs[evt.previousIndex].selected.visible = false;
        }
        evt.tab.selected.visible = true;
    }
    

    https://gist.github.com/853935