Fixing a TableView to The Bottom on iOS -- The Appcelerator Way

You must Login before you can answer or comment on any questions.

Hey guys,

So I asked something over on stackoverflow to see if what I wanted to do was even possible, as I can't find a way to do it in Appcelerator. What I'm trying to do is make a tableView similar to the Messages app on iOS that I can do a couple things with.

Note, these are fake methods and won't actually work currently...

var data = [];
 
var tableView = Ti.UI.createTableView({
    height:200,
    width:Ti.UI.FILL,
    data:data,
    fixTo:'bottom' // The purpose here is to set it so that the tableView is fixed to the bottom vs the top. Content is then added upwards (if that makes sense) OR, if it's easier. Content is added the same, but the tableView just remains fixed to the bottom.
});
 
var row = Ti.UI.createTableViewRow({
    backgroundColor:'#ff0000',
    height:44,
    width:Ti.UI.FILL
});
 
tableView.appendRow(row); // This would just add the new message or whatever to the bottom of the tableview
tableView.scrollToBottom(); // This would scroll the tableView back to the bottom
I'd love to impliment something like this in Appcelerator, but honestly I know nothing about creating custom modules. Even if I did, is it possible to extend the Appcelerator TableView without copying the entire TiUiTableView files into the new custom module? Sorry if that's a stupid question, like I said, I know almost, if not exactly, nothing about creating appcelerator modules (although I wouldn't hate to learn).

As I mentioned, I asked over on stack overflow if it was possible to do what I wanted to do, and here's what I got as a response:

stackoverflow question

It seems like it should be pretty simple... but what do I know.

Thanks!

1 Answer

Accepted Answer

HI Michael

The tableView is just a special type of scrollView, the scrollView is a better fit for your needs as you do not have to try and 'fit' into the specifics of the tableView.

The code below shows how easy it is to achieve your request without any custom modules. You should be able to adapt it to your needs.

This is a proof of concept fully working code sample for you.

var win = Titanium.UI.createWindow({
    title : 'Win 1',
    backgroundColor : '#fff'
});
var tab1 = Titanium.UI.createTab({
    icon:'KS_nav_views.png',
    title:'Tab 1',
    window: win
});
 
var intContentCount = 0;
 
var scrollView = Ti.UI.createScrollView({
    backgroundColor: 'orange',
    contentHeight: 'auto',
    contentWidth: 'auto',
    height: Ti.UI.FILL,
    showHorizontalScrollIndicator: true,
    showVerticalScrollIndicator: true,
    width: Ti.UI.FILL
});
win.add(scrollView);
 
var view = Ti.UI.createView({
    backgroundColor: 'yellow',
    bottom: 0,
    height: Ti.UI.SIZE,
    layout: 'vertical',
    width: Ti.UI.FILL
});
scrollView.add(view);
 
var btnTest = Ti.UI.createButton({
    title: 'add another'
});
btnTest.addEventListener('click', function (e) {
    intContentCount = intContentCount + 1;
    // just adding a label here but your could add
    // a view and put a label and button inside if you wish
    var lbl = Ti.UI.createLabel({
        height: Ti.UI.SIZE,
        text: 'Example Content: ' + intContentCount,
        width: Ti.UI.FILL
    });
    view.add(lbl);
    scrollView.scrollToBottom();
});
win.setRightNavButton(btnTest);
var tabGroup = Titanium.UI.createTabGroup();
tabGroup.addTab(tab1);
tabGroup.open();

— answered 9 months ago by Malcolm Hollingsworth
answer permalink
5 Comments
  • Hi Malcolm, thanks for the great answer! Really appreciate the code sample. Recently I've tried moving to a scrollView but using tableViews seems much easier to manage as most (if not all) of the tables I'm trying to use with this use a lot of the features found just in tableViews (editing, swiping rows, appending and prepending rows, section headers, and a couple of other things).

    As a tableView is really just a glorified scrollView, is there a reason why there is no scrollToBottom(); for a tableView?

    One thought is to put the tableView inside of a scrollview and set the tableView to scrollable:false, but doing it that way I've run into some issues with the dynamic height of the tableView rows messing with the contentHeight in odd ways.

    In the above example as well, it seems as though if I had a large amount of data (say, 20 rows at 44px heights) then it would load the content in normally (fixed to the top). I could then run the "scrollToBottom();" method and it would scroll down, but it doesn't seem like the Messages app has to do that. When I click on an sms-conversation it slides over with the view already fixed to the bottom. I also noticed that they are using a TableView there for that app because it has the "edit" functionality as well in a format that is standard iOS (i mean, it doesn't look like they faked it with views inside of a scrollView).

    Forgive me if I'm just missing something... it's very possible, I'm on coffee number 4

    — commented 9 months ago by Michael Fogg

  • Hi Michael

    You are welcome. I have adapted some previous test code I did which I used for someone else's answer - so there is more detail in this one - so enjoy the extras.

    You can use a table but it is not ideally suited to managing the content height after it has been created. That said this solution does work by combining a non-scrollable tableView and scrollView together and you will need to use the setData command as the appendRow does not update the height of the table after the fact.

    You should be able to use this code stand-alone as you did with the previous one.

    var tab2;
    (function () {
        var win = Titanium.UI.createWindow({  
            backgroundColor: 'transparent',
            title: 'Tab 2'
        });
        tab2 = Titanium.UI.createTab({  
            icon: 'KS_nav_views.png',
            title: 'Tab 2',
            window: win
        });
        var intContentCount = 0;
     
        function addRow(obj) {  
            Ti.API.info('title', obj.title);
            Ti.API.info('subtitle', obj.subtitle);
            var row = Ti.UI.createTableViewRow({
                height: Ti.UI.SIZE,
                backgroundColor: '#fff',
                hasChild: true,
                width: Ti.UI.FILL
            });
            var viewParent = Ti.UI.createView({
                bottom: 10, // use as margin from the row edges
                height: Ti.UI.SIZE,
                layout: 'vertical',
                left: 10, // use as margin from the row edges
                right: 10, // use as margin from the row edges
                top: 10, // use as margin from the row edges
                width: Ti.UI.FILL    
            });
            row.add(viewParent);
            var lblTitle = Ti.UI.createLabel({
                font: {
                    fontSize: 14
                },
                height: Ti.UI.SIZE,
                left: 0,
                text: (obj.title || ''),
                width: Ti.UI.WIDTH
            });
            viewParent.add(lblTitle);
            if (obj.subtitle) {
                var lblSubTitle = Ti.UI.createLabel({
                    color: '#666', // used for contrast only
                    font: {
                        fontSize: 14
                    },
                    height: Ti.UI.SIZE,
                    left: 0,
                    text: (obj.subtitle || ''),
                    top: 2, // this will place 2 pixels between the title and the subtitle - adjust to suit your needs
                    width: Ti.UI.WIDTH
                });
                viewParent.add(lblSubTitle);
            }
            return row;
        }
        var data = [
            { title: 'One', subtitle: '' },
            { title: 'Two', subtitle: 'This is a longer line to work with and should wrap to another line' },
            { title: 'Three', subtitle: ''  },
            { title: 'Four', subtitle: 'Shorter line' },
            { title: 'Five', subtitle: ''  },
            { title: 'Six', subtitle: ''  },
            { title: 'Seven', subtitle: ''  },
            { title: 'Eight', subtitle: ''  },
            { title: 'Nine', subtitle: ''  },
            { title: 'Ten', subtitle: 'Shorter line' },
            { title: 'Eleven', subtitle: ''  },
            { title: 'Twelve', subtitle: ''  },
            { title: 'Thirteen', subtitle: ''  },
            { title: 'Fourteen', subtitle: 'Shorter line' },
            { title: 'Fifteen', subtitle: 'This is a longer line to work with and should wrap to another line' },
            { title: 'Sixteen', subtitle: 'This is a longer line to work with and should wrap to another line, This is a longer line to work with and should wrap to another line.' },
            { title: 'Seventeen', subtitle: ''  },
            { title: 'Eighteen', subtitle: ''  }
        ];
        var rows = [], intRow;
        //var intRows = data.length; // long list - exceeds screen height
        var intRows = 5; // short list shorter than the screen height
        for (intRow = 0; intRow < intRows; intRow = intRow + 1) {
            intContentCount = intContentCount + 1;
            rows.push(addRow({
                title: data[intRow].title,
                subtitle: data[intRow].subtitle
            }));
        }
        var scrollView = Ti.UI.createScrollView({
            contentHeight: Ti.UI.SIZE,
            contentWidth: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            layout: 'vertical',
            showHorizontalScrollIndicator: true,
            showVerticalScrollIndicator: true,
            bottom: 0,
            width: Ti.UI.FILL
        });
        win.add(scrollView);
        var tbl = Ti.UI.createTableView({
            data: rows,
            height: Ti.UI.SIZE,
            scrollable: false,
            bottom: 0,
            width: Ti.UI.FILL
        });
        scrollView.add(tbl);
     
        var btnTest = Ti.UI.createButton({
            title: 'add another'
        });
        btnTest.addEventListener('click', function (e) {
            intContentCount = intContentCount + 1;
            var row = addRow({
                title: 'Example Content: ' + intContentCount            
            });
            rows.push(row);
            tbl.setData(rows);
            scrollView.scrollToBottom();
            Ti.API.info('intContentCount', intContentCount);
        });
        win.setRightNavButton(btnTest);
    })();
     
    var tabGroup = Titanium.UI.createTabGroup();
    tabGroup.addTab(tab2);
    tabGroup.open();

    — commented 9 months ago by Malcolm Hollingsworth

  • Malcolm, ok! So that's really good progress, thanks for that. So I took a look at that, and it does a great job of actually adding the content in (when clicking the right nav button) and appending new rows/scrolling. I think that the piece that it is missing is the piece that I'm looking for. So, here's a slightly modified version of your above code (had to split it into two because it's long):

    var tab2;
    (function () {
        var win = Titanium.UI.createWindow({  
            backgroundColor: 'transparent',
            title: 'Tab 2'
        });
        tab2 = Titanium.UI.createTab({  
            icon: 'KS_nav_views.png',
            title: 'Tab 2',
            window: win
        });
        var intContentCount = 0;
     
        function addRow(obj) {  
            Ti.API.info('title', obj.title);
            Ti.API.info('subtitle', obj.subtitle);
            var row = Ti.UI.createTableViewRow({
                height: Ti.UI.SIZE,
                backgroundColor: '#fff',
                hasChild: true,
                width: Ti.UI.FILL
            });
            var viewParent = Ti.UI.createView({
                bottom: 10, // use as margin from the row edges
                height: Ti.UI.SIZE,
                layout: 'vertical',
                left: 10, // use as margin from the row edges
                right: 10, // use as margin from the row edges
                top: 10, // use as margin from the row edges
                width: Ti.UI.FILL    
            });
            row.add(viewParent);
            var lblTitle = Ti.UI.createLabel({
                font: {
                    fontSize: 14
                },
                height: Ti.UI.SIZE,
                left: 0,
                text: (obj.title || ''),
                width: Ti.UI.WIDTH
            });
            viewParent.add(lblTitle);
            if (obj.subtitle) {
                var lblSubTitle = Ti.UI.createLabel({
                    color: '#666', // used for contrast only
                    font: {
                        fontSize: 14
                    },
                    height: Ti.UI.SIZE,
                    left: 0,
                    text: (obj.subtitle || ''),
                    top: 2, // this will place 2 pixels between the title and the subtitle - adjust to suit your needs
                    width: Ti.UI.WIDTH
                });
                viewParent.add(lblSubTitle);
            }
            return row;
        }
    var data = [
            { title: 'One', subtitle: '' },
            { title: 'Two', subtitle: 'This is a longer line to work with and should wrap to another line' },
            { title: 'Three', subtitle: ''  },
            { title: 'Four', subtitle: 'Shorter line' },
            { title: 'Five', subtitle: ''  },
            { title: 'Six', subtitle: ''  },
            { title: 'Seven', subtitle: ''  },
            { title: 'Eight', subtitle: ''  },
            { title: 'Nine', subtitle: ''  },
            { title: 'Ten', subtitle: 'Shorter line' },
            { title: 'Eleven', subtitle: ''  },
            { title: 'Twelve', subtitle: ''  },
            { title: 'Thirteen', subtitle: ''  },
            { title: 'Fourteen', subtitle: 'Shorter line' },
            { title: 'Fifteen', subtitle: 'This is a longer line to work with and should wrap to another line' },
            { title: 'Sixteen', subtitle: 'This is a longer line to work with and should wrap to another line, This is a longer line to work with and should wrap to another line.' },
            { title: 'Seventeen', subtitle: ''  },
            { title: 'Eighteen', subtitle: ''  },
            { title: 'One', subtitle: '' },
            { title: 'Two', subtitle: 'This is a longer line to work with and should wrap to another line' },
            { title: 'Three', subtitle: ''  },
            { title: 'Four', subtitle: 'Shorter line' },
            { title: 'Five', subtitle: ''  },
            { title: 'Six', subtitle: ''  },
            { title: 'Seven', subtitle: ''  },
            { title: 'Eight', subtitle: ''  },
            { title: 'Nine', subtitle: ''  },
            { title: 'Ten', subtitle: 'Shorter line' },
            { title: 'Eleven', subtitle: ''  },
            { title: 'Twelve', subtitle: ''  },
            { title: 'Thirteen', subtitle: ''  },
            { title: 'Fourteen', subtitle: 'Shorter line' },
            { title: 'Fifteen', subtitle: 'This is a longer line to work with and should wrap to another line' },
            { title: 'Sixteen', subtitle: 'This is a longer line to work with and should wrap to another line, This is a longer line to work with and should wrap to another line.' },
            { title: 'Seventeen', subtitle: ''  },
            { title: 'Eighteen', subtitle: ''  }
        ];
        var rows = [], intRow;
        //var intRows = data.length; // long list - exceeds screen height
        var intRows = data.length; // short list shorter than the screen height
        for (intRow = 0; intRow < data.length; intRow++) {
            intContentCount = intContentCount + 1;
            rows.push(addRow({
                title: data[intRow].title,
                subtitle: data[intRow].subtitle
            }));
        }
     
        var scrollView = Ti.UI.createScrollView({
            contentHeight: Ti.UI.SIZE,
            contentWidth: Ti.UI.FILL,
            height: Ti.UI.SIZE,
            layout: 'vertical',
            showHorizontalScrollIndicator: true,
            showVerticalScrollIndicator: true,
            bottom: 0,
            width: Ti.UI.FILL
        });
     
        var tbl = Ti.UI.createTableView({
            data: rows,
            height: Ti.UI.SIZE,
            scrollable: false,
            bottom: 0,
            minRowHeight:40,
            width: Ti.UI.FILL
        });
        scrollView.add(tbl);
     
        //scrollView.setContentOffset({x:0, y:10000},false);
        //scrollView.scrollToBottom();
        var btnTest = Ti.UI.createButton({
            title: 'add another'
        });
        btnTest.addEventListener('click', function (e) {
            intContentCount = intContentCount + 1;
            var row = addRow({
                title: 'Example Content: ' + intContentCount            
            });
            rows.push(row);
            tbl.setData(rows);
            scrollView.scrollToBottom();
            Ti.API.info('intContentCount', intContentCount);
        });
        win.setRightNavButton(btnTest);
        win.add(scrollView);
    })();
     
    var tabGroup = Titanium.UI.createTabGroup();
    tabGroup.addTab(tab2);
    tabGroup.open();
    If you open that, you'll notice that the tableView starts scrolled to the top, then you can scroll it to the bottom manually. What I'm trying to do is make it so that when the tableView first loads, it's at the bottom already. Then using something just like your code above to append future rows to it (which your code does great).

    The only way I can think of currently to do what I want to do (start at bottom) is to do all of the code above and to hide the scrollView then show it again once loaded, but seeing as it's possible using the iOS SDK (not appcelerator) as the Messages app does it, I just feel like there may be a better way (if that suggested way even works).

    — commented 9 months ago by Michael Fogg

  • Show 2 more comments

Your Answer

Think you can help? Login to answer this question!