Twitter-like swipe on tableview

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

Ok, so I have been trying to do this various different ways for months now, and I think it's time I ask for help.

What I'm trying to do is set a tableview row to be "swipable" so that when you swipe it it displays a view underneath it with action buttons.

![Twitter swipe view](http://i45.tinypic.com/2884duh.png "Twitter Swiped View -- The End Goal")

I've tried everything I can think of to do it, but for one reason or another it never works!

The biggest issue is that the content is dynamic, so I have no idea how tall the message is going to be. So for that reason (Using Titanium 2.0.2 and iOS 5) I have to use "Ti.UI.SIZE" for the height of the row.

I threw together a quick example of what I last tried, although this is only one of the probably dozen ways I've tried to hack it together (including counting characters and setting the height that way, which didn't work because 10 "W's" is a lot longer than 10 "i's") it does lead to some interesting questions that might help find the right direction.

Example: Creating a row_visible and row_hidden view and placing those in the row, and setting the swipe on those instead of the row itself.

//app.js
 
Titanium.UI.setBackgroundColor('#000');
 
var isSwiping = false;
 
function setSwipe(element, leftFunction, rightFunction){isSwiping = false;
    element.addEventListener('swipe', function(e) {
        if (isSwiping) { return; } //make sure it only fires once
        isSwiping = true;       
        if (e.direction === 'right') {
            rightFunction(e);
            isSwiping = false;
        } else if (e.direction === 'left') {
            leftFunction(e);
            isSwiping = false;
        }
    });
};
 
var win = Titanium.UI.createWindow({  
    title:'Tab 1',
    backgroundColor:'#fff'
});
 
var tableView = Titanium.UI.createTableView({
    top:0,
    height:Ti.Platform.displayCaps.platformHeight,
    width:Ti.Platform.displayCaps.platformWidth
});
 
var row_1 = Ti.UI.createTableViewRow({height:Ti.UI.SIZE,hasChild:false});
var row_1_label = Titanium.UI.createLabel({height:Ti.UI.SIZE, text:"This is the first rows content. Not sure how long this will be so I'm just filling it with content."});
 
var row_1_visible = Ti.UI.createView({backgroundColor:'#ddd',height:Ti.UI.SIZE,width:Ti.UI.FILL,layout:'vertical'});
var row_1_hidden = Ti.UI.createView({top:0,height:Ti.UI.FILL, width:Ti.UI.FILL,backgroundColor:'#ff0000'}); 
 
row_1_visible.add(row_1_label); //Add my text to the visible view
row_1.add(row_1_hidden); // Add this one first so it's below the visible view
row_1.add(row_1_visible);
 
setSwipe(row_1_visible, function(){
    row_1_visible.animate({opacity:0.0, duration:200}, function(){
        row_1_visible.hide();
    });
    Ti.API.info('Row Visible Swiped Left');
}, function(){
    row_1_visible.animate({opacity:0.0, duration:200}, function(){
        row_1_visible.hide();
    });
    Ti.API.info('Row Visible Swiped Right');
});
 
setSwipe(row_1_hidden, function(){
    row_1_visible.show();
    row_1_visible.animate({opacity:1.0, duration:200});
    Ti.API.info('Row Hidden Swiped Left');
}, function(){
    row_1_visible.show();
    row_1_visible.animate({opacity:1.0, duration:200});
    Ti.API.info('Row Hidden Swiped Right');
});
 
var data = [row_1];
tableView.setData(data);
 
win.add(tableView);
 
win.open();
What happens in the above example is first the row_1_hidden stretches the entire height of the app, which also makes the row stretch the entire height of the app. This tells me that Ti.UI.FILL takes precedence over the Ti.UI.SIZE and means its not bounded to any parent size (ie the height of the row because of the height of the label). That kind of makes sense, as the row has no fixed size so Ti.UI.FILL probably has no bounding area to fill other than the last one with a fixed height (The tableView) which it fills. That's unfortunate, but ok, I get it.

The next issue in the above example, is that when you swipe that row, the row_1_visible disappears (PERFECT). Unfortunately, as soon as you swipe the row_1_hidden it disappears and the row_1_visible appears. That would make sense and is what I wanted, but oddly... the row_1_hidden is gone for good (kind of...). It is technically still there as future swipe events on it still work, but the actual red background on it is gone! So it's no longer visible at all. Why would that be? If you take a look at that code above, at no point did I specify or say that row_1_hidden should go away! It should just go behind the now-visible row_1_visible, shouldn't it?

So I guess the main two questions are:

  1. Where is row_1_hidden going when I swipe it to make row_1_visible appear again?
  2. The big one... how do you make a view stretch to the height of its parent view... which has a dynamic height? Or is that not possible.

Sorry about the long-winded-ness of this post!

— asked 12 months ago by Michael Fogg
2 Comments
  • Apparently the image is not showing, must have done it wrong (and can't seem to edit it...). Anyways not a huge deal, it's just a picture of the twitter swipe view.

    http://oi45.tinypic.com/2884duh.jpg

    — commented 12 months ago by Michael Fogg

  • Interesting, it seems like the first issue is solved by using views in a scrollview instead of tableview rows in a tableview... So it looks like maybe the tableview row is trying to optimize it by "removing" views that wouldn't be visible?

    — commented 12 months ago by Michael Fogg

2 Answers

Accepted Answer

— answered 12 months ago by Dan Tamas
answer permalink
5 Comments
  • Really cool! Thanks for taking the time. I'm going to take a deeper look at it a little bit later this weekend but it seems to work perfectly!

    — commented 12 months ago by Michael Fogg

  • I have used this code but I get the following error with the android version...

    Message: Uncaught TypeError: Cannot read property 'v2' of undefined. Source: current_row.v2.animate({

    any idea?

    — commented 11 months ago by Sergio Cabral

  • Hey Sergio, I haven't tried it on android yet but, does it mention what line (or which "current_row.v2.animate({") the error is in? It seems like the current_row is not being set, so knowing where in the code that's happening may help find the source of the issue.

    — commented 11 months ago by Michael Fogg

  • Show 2 more comments

What about setting the "opening" view as a different row that animates it's height from 0 to whatever you need next below the row u just swiped to ? So a "floating" row that you insert after the swiped row.

— answered 12 months ago by Dan Tamas
answer permalink
10 Comments
  • Hey Dan, thanks for chiming in! I was actually just about to post something that I just did, although it isn't the answer to my question, it's a test to see what CAN be done. So my thought was, if it's not possible to do the swipe and hiding/showing a view behind it, can a row be placed right after it? Then it would act more as a drawer that drops down below it than the original swipe affect that I'm going for... but is better than nothing! What I just did as a test was (excuse the hackyness):

    var open_drawer =  -1;
     
    var row = new TestRow({test_var:e.row.test_var}); 
    var scroll_to = true; // set it to scroll to that row unless i tell it not to
    this_index = e.index; // store this so it doesnt change when deleting a row, just incase
    if(open_drawer >= 0){ // do this if there is an open drawer
        if (this_index+1 == open_drawer){ //your clicking the open item...
               tableView.deleteRow(open_drawer); //swiped row plus 1 should be the row under it!
               open_drawer = -1; // remove the swiped row as none are open
               scroll_to = false; //dont bother scrolling to rows their already on
        } else if (this_index < open_drawer){ // you clicked a row above the drawer
              tableView.deleteRow(open_drawer);
              tableView.insertRowAfter(this_index,row);
              open_drawer = this_index+1;
             } else if (this_index > open_drawer){ // you clicked a row below the drawer
              tableView.deleteRow(open_drawer);
              tableView.insertRowAfter(this_index-1,row);
              open_drawer = this_index;
             };
    } else{ // no open drawers so just throw it under whichever one you click
              tableView.insertRowAfter(this_index,row);
              open_drawer = this_index+1;
    };
     
    if(scroll_to){
        tableView.scrollToIndex(open_drawer-1,{animated:true,position:Ti.UI.iPhone.TableViewScrollPosition.TOP});
    };
     
    Ti.API.info(open_drawer); // lets just see which row index is currently the open one

    — commented 12 months ago by Michael Fogg

  • WOw that looks horrible... sorry! I'd erase it if I could, the comments are useful but apparently look like a mess...

    var open_drawer =  -1;
     
    var row = new TestRow({test_var:e.row.test_var}); 
    var scroll_to = true; 
    this_index = e.index; 
    if(open_drawer >= 0){
     if (this_index+1 == open_drawer){
       tableView.deleteRow(open_drawer);
       open_drawer = -1; 
       scroll_to = false;
     } else if (this_index < open_drawer){
       tableView.deleteRow(open_drawer);
       tableView.insertRowAfter(this_index,row);
       open_drawer = this_index+1;
     } else if (this_index > open_drawer){
       tableView.deleteRow(open_drawer);
       tableView.insertRowAfter(this_index-1,row);
       open_drawer = this_index;
     };
    } else{
       tableView.insertRowAfter(this_index,row);
       open_drawer = this_index+1;
    };
     
    if(scroll_to){
     tableView.scrollToIndex(open_drawer-1,{animated:true,position:Ti.UI.iPhone.TableViewScrollPosition.TOP});
    };
     
    Ti.API.info(open_drawer);

    — commented 12 months ago by Michael Fogg

  • so is working?

    — commented 12 months ago by Dan Tamas

  • Show 7 more comments

Your Answer

Think you can help? Login to answer this question!