Facebook-like Static Menu - iOS

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

I'm on a mission to try to find out how some of the big guys' apps would function in appcelerator. A week or two ago, Dan Tamas did an amazing job figuring out how twitter handles their swipe-to-reveal feature on tableviews (http://developer.appcelerator.com/question/139133/twitter-like-swipe-on-tableview) and I've got two more questions I have left to figure out, so just posting one here incase someone has any hints or can point me in the right direction to start!

Facebook-like Static Menu If you have ever used the Facebook mobile app (and if you have an iPhone I'm assuming you probably have) you may have noticed that when you navigate pages the top menu bar (with the three buttons) never actually moves! The top left/right nav buttons change depending on whether you can go back to the previous page or not, but the three buttons stay in the same place at all times! Really cool stuff, because it allows them to show how many notifications you have wherever you are in the app.

So the question there is... How does the Facebook app create/manage back buttons without ever actuallyrefreshing that top menu? AND, is that top menu then just a window placed over a one-tab tabgroup (or a navgroup)?

If what I'm saying above is right.. there would have to be 3 windows stacked on top of each other... Is that even possible?

My initial thought:

1) Initial window is a menu window opened first so it is below the content win (to create facebook's slider menu effect).

2) Second is a globally referenced tabgroup with one tab thats content can be changed like "globals.tabGroup.currentTab.open(newWindow);". IF that's the case, we would also probably have to keep track of the current window in a "globals.tabGroup.currentWindow;" type variable so that we can close it from another window (outside of the tab group). This window could also (maybe?) be set to keep a constant global variable of how many "layers" of windows it has open, and use that to change the top left and top right navbar buttons in the 3rd window to either "back" or "menu" (if it's the initial window in the stack).

3) A 3rd window opened on top that holds the top navigation. This would have a button in the top left that slid both the tabGroup and itself (the top menu) to the right or left to reveal/hide the bottom "hidden" menu.

Sorry about the lengthy post! I also may be completely over-thinking this...If anyone wants me to do a mockup image of what I'm thinking, let me know and I'd be happy to.

Thanks,

Mike

1 Answer

Accepted Answer

Hi Michael

Have you considered that the app has no tabgroup and is simply a single for the main interface, with several views playing the part of what you consider windows inside tabs if it were a strictly traditional tabgroup app.

By using main layered views you can control the layout, particularly with the top left button revealing a 'hidden' view. The 'nav bar' at the top is most likely another view dressed up to appear as a standard 'nav bar', however by simply hiding and showing (and sliding) views in and out it all appears to be a standard nav bar.

The benefit of this method is as you state is that it appears to work visually well. The slight downside is that you have to manage the process of fake windows (actually views) sliding in and out rather than the built in ability of the tab group and nav controller.

Instead of considering the method to be 3 main windows, instead think 3 views, add them in the correct order and position them on and off screen for their ideal starting points.

A rough example structure would be;

// contains the search bar and tableview
// which is hidden behind the main view
var viewSections = Ti.UI.createScrollView({
    contentHeight: Ti.UI.SIZE,
    contentWidth: Ti.UI.FILL,
    layout: 'vertical',
    showHorizontalScrollIndicator: true,
    showVerticalScrollIndicator: true
});
win.add(viewSections);
var search = Ti.UI.createSearchBar({
...
});
viewSections.add(search);
var sections = [
    { title: 'One' },
    { title: 'Two' },
    { title: 'Three' }
];
var tblSections = Ti.UI.createTableView({
    data: data
...
});
viewSections.add(data);
 
// contains the main information you want to present
// has the 'nav bar' at the top
var viewMain = Ti.UI.createScrollView({
    contentHeight: Ti.UI.SIZE,
    contentWidth: Ti.UI.FILL,
    layout: 'vertical',
    showHorizontalScrollIndicator: true,
    showVerticalScrollIndicator: true
});
win.add(viewMain);
var viewMain1 = Ti.UI.createView({
...
});
viewMain.add(viewMain1);
var viewMain2 = Ti.UI.createView({
...
});
viewMain.add(viewMain2);
 
// generix view used to show context information
// with this you could swap info in and out as required
// you could have several of these if you needed but be cautious 
// with memory usage
var viewOther = Ti.UI.createScrollView({
    contentHeight: Ti.UI.SIZE,
    contentWidth: Ti.UI.FILL,
    layout: 'vertical',
    showHorizontalScrollIndicator: true,
    showVerticalScrollIndicator: true
});
win.add(viewOther);
var viewImage = Ti.UI.createImageView({
...
});
viewOther.add(viewImage);

— answered 10 months ago by Malcolm Hollingsworth
answer permalink
10 Comments
  • Malcolm, great idea! I hadn't really thought about the possibility of a near-windowless solution like this. I guess a couple questions come to mind right off the bat for me.

    The first is, in this setup, how would you load in a view with new data? For example, in the Facebook app you'll notice that you can click on a user and it goes to their page. If that "user page" was then just another view dressed up and animated in to look like a new window, would the process then be:

    1. Create the new view "off camera" and animate in by loading in a new common.js window like
      var open_views = [];
      UserView = require(/UserView.js) // this view starts with a left margin of 320
       
      var openUserView = function(user){
          user_view = new UserView({user:user}); 
              open_views.push(user_view);
              win.add(user_view);
              user_view.animate({
                    left:0,
                    duration:300
              });
      };
       
      user_button.addEventListener('click', function(e){
             openUserView(e.source.user); // pushing the user_id and maybe a little more info to get json
      });
    2. That views animation in would then need some sort of callback to trigger the top nav-view to manage it's back button when the new view was added... something like this?:
      var animateViewIn = Ti.UI.createAnimation();
      animateViewIn.left = 0;
      animateViewIn.duration = 300;
      animateViewIn.addEventListener('complete', function(e){
            manageNavBackButton();
      });
       
      var manageNavBackButton = function(){
             if (open_views.length > 0){
                  // show back button
            } else {
                  // show menu button   
            }
      };
    3. That view would then need an animation out when that back button was clicked, but would that also need to "destroy" that view?
      var animateViewOut = Ti.UI.createAnimation();
      animateViewOut.left = 320;
      animateViewOut.duration = 300;
      animateViewOut.addEventListener('complete', function(e){
            //not sure if I can use e to reference the animating object so for now I'll just say close the last open view
            win.remove(open_views[open_views.length-1]); //is this even needed? Probably...or something like it
            open_views.pop(); // remove the last view from the array
            manageNavBackButton();
      });
      I think you're on the right track with this, because after a little bit more playing around with the facebook app I realized that pages actually come in on top of one another. So, for example, if I'm on my own page and I click on a user. I can see my page in the background stay in place (not slide out to the left like a normal nav/tabgroup would do) and another "window" just slide over the top of it.

    Excuse the code and probable errors... that's what I get for typing directly in this window! Thanks for the help already, leading me in a new direction I hadn't thought of.

    — commented 10 months ago by Michael Fogg

  • You're welcome - do not forget to mark as a suitable answer if you think it is.

    What I would do is add a CommonJS module to handle the different types of view 'Windows', obviously you can then use this principle for all features. Add each one to an array and remove from scope as soon as they are not needed. Remember that whilst this solution is simple and elegant it could end up being a memory hog.

    Quick CommonJS starting point user.js

    exports.version = 1.0;
    exports.author = 'Malcolm Hollingsworth';
     
    function createUser(obj) {
        obj = obj || {};
        // parent view - faux window
        var viewUser = Ti.UI.createScrollView({
            contentHeight: Ti.UI.SIZE,
            contentWidth: Ti.UI.FILL,
            id: obj.id || undefined, // this is used soley for back button function to pass back
            layout: 'vertical',
            showHorizontalScrollIndicator: true,
            showVerticalScrollIndicator: true
        });
        // basic title bar
        var viewTitle = Ti.UI.createView({
            backgroundColor: obj.titleBackgroundColor || '#c60000',
            color: obj.titleColor || '#c60000',
            height: 44,
            top: 0,
            width: Ti.UI.FILL
        });
        viewUser.add(viewTitle);
        var lblTitle = Ti.UI.createLabel({
            height: Ti.UI.SIZE,
            text: obj.title || 'Title',
            width: Ti.UI.SIZE
        });
        viewTitle.add(lblTitle);
        var btnBack = Ti.UI.createButton({
            height: 30,
            left: 20,
            title: 'Back'
            width: 50
        });
        viewTitle.add(btnBack);
     
        // actual place content goes
        // users a scroll view for obvious reasons
        var viewPage = Ti.UI.createScrollView({
            contentHeight: Ti.UI.SIZE,
            contentWidth: Ti.UI.FILL,
            layout: 'vertical',
            showHorizontalScrollIndicator: true,
            showVerticalScrollIndicator: true,
            top: 44
        });
        viewUser.add(viewPage);
     
        var imgPhoto = Ti.UI.createImageView({
            height: Ti.UI.SIZE,
            image: 'photo.png',
            top: 20,
            width: Ti.UI.SIZE
        });
        viewPage.add(imgPhoto);
        var lblUserName = Ti.UI.createLabel({
            height: Ti.UI.SIZE,
            text: 'Users Name',
            top: 20,
            width: Ti.UI.SIZE
        });
        viewPage.add(lblUserName);
     
        if (obj.back) {
            btnBack.addEventListener('click', function (e) {
                obj.back({
                    id: viewUser.id,
                    someotherproperty: 'If you need more'
                });
            });
        }
        return viewUser;
    }
     
    exports.createUser = createUser;
    Quick Calling Example;
    var App = {};
    App.User = require('user');
     
    App.User.createUser({
        title: 'title',
        titleBackgroundColor: 'orange',
        titleColor: 'white',
        back: function (e) {
            alert('remove this window [' + e.id ']');
        }
    });
    This should give you the starting point for users you need. You can put as many other items into the commonJS createUser function to suit your need, just remember you need to do a little more layout because of the fake title bar etc. But given your code examples above I think you are fine.

    Remember to add the off-screen starting pointing, you can add the animation to move the view off screen inside back button function and use the call back to handle the removal of the user view from your array.

    I would consider adding a reference number to each view so that in your remove function you can loop through the array of view check the ID number and then remove, as you may not be able to assume the one being removed is the last in the array stack.

    Apologies for any bugs/typos, I quickly threw this together.

    — commented 10 months ago by Malcolm Hollingsworth

  • Sorry - I make a mistake. Last code block should have ended.

    open_views.push(App.User.createUser({
        title: 'title',
        titleBackgroundColor: 'orange',
        titleColor: 'white',
        back: function (e) {
            alert('remove this window [' + e.id ']');
        }
    }));

    — commented 10 months ago by Malcolm Hollingsworth

  • Show 7 more comments

Your Answer

Think you can help? Login to answer this question!