Titanium Mobile: Create a Sliding Menu for iOS

Titanium Mobile: Create a Sliding Menu for iOS

Tutorial Details
  • Completion Time: 30 Minutes
  • Difficulty: Intermediate
  • Technology: Titanium Mobile

This tutorial will teach you how to build a sliding menu similar to the one featured in the Facebook application using Titanium Mobile.


Step 1: Getting Started

The sliding menu consists of a full-sized window (the main window) on top of a smaller one that contains a table view (the menu). To create the sliding effect, we’ll have to trigger an animation that will track and follow a touch event horizontally. However, let’s save that for later. For now, we’ll start by setting up the windows.

First, we’ll create the menu:

//// ---- Menu window, positioned on the left
var menuWindow = Ti.UI.createWindow({
	top:0,
	left:0,
	width:150
});
menuWindow.open();
//// ---- Menu Table
// Menu Titles
var menuTitles = [
	{title: 'Menu 1'},
	{title: 'Menu 2'},
	{title: 'Menu 3'},
	{title: 'Menu 4'},
	{title: 'Menu 5'},
	{title: 'Menu 6'}
];
// Tableview
var tableView = Ti.UI.createTableView({
	data:menuTitles
});
menuWindow.add(tableView);

This is a very basic table view setup, but it will do. So you should now have something like the following:


Step 2: Main Window

Now we need a window with a navigation bar and a button in it that will allow us to open the menu with an animation. So, in order to achieve this, we actually need two windows: one containing a navigationGroup (indispensable in order to have a navigation bar) and another one that is in the navigationGroup:

//// ---- Window with navigationGroup
var navWindow = Ti.UI.createWindow({
    width:320 // Set the width of the sliding window to avoid cut out from animation
});
navWindow.open();
// Main window
var win = Ti.UI.createWindow({
	title:'Main Window',
	backgroundColor:'#28292c',
	barColor:'#28292c'
});
// NavigationGroup
var navGroup = Ti.UI.iPhone.createNavigationGroup({
	window:win
});
navWindow.add(navGroup);
// Top left button
var menuButton = Ti.UI.createButton({
	title:'Menu',
	toggle:false // Custom property for menu toggle
});
win.setLeftNavButton(menuButton);

Hey, you probably noticed that toggle:true property in our button, right? It doesn’t really exist; it’s a custom property that I added. You can pretty much name it however you want or even create a variable for it (like var toggle = true;) if it makes you feel more comfortable. However, I recommend you use this little trick because it is really handy when you have a lot of custom properties in your app.

Here is our main window:


Step 3: Toggle Menu

Okay, now we’re going to animate our window so it slides from left to right when we press the “Menu” button.

Let’s see how it works:

menuButton.addEventListener('click', function(e){
	// If the menu is opened
	if(e.source.toggle == true){
		navWindow.animate({
			left:0,
			duration:400,
			curve:Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
		});
		e.source.toggle = false;
	}
	// If the menu isn't opened
	else{
		navWindow.animate({
			left:150,
			duration:400,
			curve:Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
		});
		e.source.toggle  = true;
	}
});

You see that when we click the button, we call function(e), where e is our object (the button). By calling e.source.toggle, we are checking the custom “toggle” property discussed above (you can also use menuButton.toggle, it’s the same thing). If it is false, we’re moving our window to the right and switching the property to true. So, of course, if it’s true, the window goes back to normal and our property is then set to false again.

The curve:Ti.UI.ANIMATION_CURVE_EASE_IN_OUT is just a way to smooth the animation.


Step 4: Tracking

Yeah, this is looking pretty neat, right? But that was the easy part, because now we are getting serious! We’ll track a touch event so that we can reveal the menu by moving the main window horizontally. But before that, we’ll add some custom properties:

// Main window
var win = Ti.UI.createWindow({
	title:'Main Window',
	backgroundColor:'#28292c',
	barColor:'#28292c',
	moving:false, // Custom property for movement
	axis:0 // Custom property for X axis
});

Again, you can name these properties however you want, or you can even create dedicated variables for them, but I strogly recommend you use this method because it saves memory and it is easier to read than a bunch of variables scattered over your nice file.

Now we’re going to use the touchstart event to get the position of our finger when it touches the screen:

win.addEventListener('touchstart', function(e){
	// Get starting horizontal position
	e.source.axis = parseInt(e.x);
});

Here we take the horizontal coordinate (e.x) of our event, parse it as an integer, and then save it in our custom property axis.

Next we are going to animate the window depending on the position of our finger:

win.addEventListener('touchmove', function(e){
	// Subtracting current position to starting horizontal position
	var coordinates = parseInt(e.globalPoint.x) - e.source.axis;
	// Detecting movement after a 20px shift
	if(coordinates > 20 || coordinates < -20){
		e.source.moving = true;
	}
	// Locks the window so it doesn't move further than allowed
	if(e.source.moving == true && coordinates <= 150 && coordinates >= 0){
		// This will smooth the animation and make it less jumpy
		navWindow.animate({
			left:coordinates,
			duration:20
		});
		// Defining coordinates as the final left position
		navWindow.left = coordinates;
	}
});

To prevent the window from moving everytime we touch it, we are waiting for a movement over 20 pixels before animating. We track our touch coordinate with e.globalPoint.x and subtract it to our starting point (axis) so we can move the window. Also, it can not slide beyond the menu width (150 pixels) or beyond the left side of the screen.


Step 5: Back to Normal

If you try to run your app, you’ll see that your window will stay exactly where you leave it. That’s not what we want. We need to reposition it when the touch event is over, so it will open/close itself depending on where it is:

win.addEventListener('touchend', function(e){
	// No longer moving the window
	e.source.moving = false;
	if(navWindow.left >= 75 && navWindow.left < 150){
		// Repositioning the window to the right
		navWindow.animate({
			left:150,
			duration:300
		});
		menuButton.toggle = true;
	}else{
		// Repositioning the window to the left
		navWindow.animate({
			left:0,
			duration:300
		});
		menuButton.toggle = false;
	}
});

When our finger no longer touches the screen, the touchend event is fired, so we can adjust the position of our window.


Conclusion

We’re done! As you see, we used a very basic animation and math to achieve a great and professional effect. I really hope you enjoyed this tutorial and learned a few new tricks!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • leitom

    Good to se titanium tutorials again!

  • strassenwischer

    Yes, please more titanium Tutorials. Thank you!!

  • NONE

    how change window???? when press tittle of table view?

  • Paul Cisneros

    Source?

  • Skarf

    What would need to be changed to ensure Android compatibility?

    • http://twitter.com/jonathanpwheat Jonathan Wheat

      There is a Sliding menu that works on Android and iOS coded up by a fellow developer I know – https://github.com/appersonlabs/MASlidingMenu, its pretty flexible too

      • Evan

        Jonathan,

        I checked this out yesterday (I’ve been looking for a Titanium implementation of an iOS/Android sliding menu feature) and was unable to get it to work on the iOS simulator- I can’t get past the initial screen. Have you used it before?

      • Evan

        This is really good, I just pulled it- yes, this accomplishes this entirely programmatically, it seems.

    • Evan

      I just started using Titanium recently, but from what I can see this code is calling iOS-specific Titanium UI classes (i.e., TI.UI.iPhone.createNavigationGroup) for some of the implementation. I looked online at Titanium’s mobile API reference and there doesn’t seem to be a comparable Android-specific Titanium UI class, nor does there appear to be a comparable regular Titanium.UI class that would apply to both iOS and Android for createNavigationGroup. I was looking for more clarification about this myself, but it would seem that Android compatibility would involve doing this feature almost completely programmatically, maybe with a combination of existing Titanium.UI elements. Does anyone have any other explanations?

  • TiTaNium

    How did you solve the problem of adding a tableview, webview or scrollview on the main window? They take all the events and fingertracking doesn’t work anymore… Facebook app and path solved it though…

  • UM

    Hi I have to admit this tutorial is awesome and I have learned a few new tricks. But I wanted to know how I can go to another page when you click a link. I see from your comments you have said “event listener to the tableview and open a new window every time a row
    is clicked. Or you can add multiple views to the main window and switch
    between them.” But if possible could you give an example as I am really stuck. Thanks!

  • NYHK

    Hi, I’m designing a custom UI for an iOS app (iPhone first then iPad) on Photoshop for a client, and while I have all of the required image sizes for the other element slices from Apple (tab bar, icons, etc), I also need to design the look & feel for a slide out menu, much like the one described here. I’m not sure what the image size should be. Is it the same as the app background itself? (i.e. 640 x 1136 on iPhone 5, etc.). I’m a designer not a developer so I only asked to provide the graphical assets.

    Thanks in advance.

  • http://www.facebook.com/maik.becker Maik Becker

    Hi thanks for this tut. You did a really great work.

    But i found no way to combine this with a webview. When i add a webview e.g. on homescreen, i cant use the touch gestures within the webview. Does anybody have a clue?

  • gianniskarmas

    Great Tut. Keep the good work.
    You just miss the win.open(); at the end of your code. Else sliding fails.
    It would be nice if you could add this to gidhub too.

    Also when application opens for the first time the menu shows for some milliseconds till code finishes executing and then hides. Is there a way to fix this?

    cheers

  • David

    I used your script to code my sliding menu. It’s pretty simple ! Thanks !

    I solved a couple of problem due to my app with a bit of effort. But the last one seems to be stronger :p
    The sliding menu must appear in all my navGroup windows. Every window contains several object like table view, buttons etc… the problem is that when I try to slide out the menu by touching a button in my window for example, Titanium gives me an error that looks like that :
    Script Error = ‘undefined’ is not an object (evaluating ‘e.globalPoint.x’) at app.js (line 38).

    It seems that buttons does not have globalPoint property… Do you any solution to make me able find the window that can be animated ?

    • Hal Gatewood

      I had the same problem and added this to the top of the ‘touchmove’ function. Stops the error but also stops the sliding. if(e.source == “[object TiUIButton]“) return;

  • Dharmik

    https://github.com/dharmik/Sliding-Menu-for-iOS-in-Alloy

    Upper Link is same code of Sliding Menu for iOS But that code is in ALLOY.

    Hope,It helpful to other.

  • http://twitter.com/nunocostapt Nuno Costa

    To all that have problems because of "e.globalPoint.x"
    just replace:

    var coordinates = parseInt(e.globalPoint.x) - e.source.axis;

    by:

    var coords = e.source.convertPointToView({x:e.x, y:e.y}, win);
    var coordinates = parseInt(coords.x, 10) - e.source.axis;

  • Carlos

    very enlightnening… I just have a question. My mainwindow is not moving but shrinking. I tried changing mainWindow with navBar and behaves the same -shrinking. What could possible be wrong?. It might be a navbar setting or window setting maybe?.

    Here’s my code (g.navBar is just a global variable that holds the navbar. I have a g.mainWindow, which holds the mainWindow. I tried with both and they yield the same bad behavior):

    function HeaderInfo() {
    var self = Ti.UI.createWindow();

    // Change title for Navbar
    var titleLabel = Titanium.UI.createLabel({
    color:’#FFF’,
    height:18,
    width:210,
    top:5,
    text:’Hazard Observation’,
    textAlign:’center’,
    font:{fontFamily:’HelveticaNeue’,fontSize:18}
    });
    self.titleControl = titleLabel;

    // LEFT button
    var leftNavBarButton = Ti.UI.createButton({
    settingsOpen: false,
    width:12,
    height:32,
    image:”images/dragThumb.png”
    });

    leftNavBarButton.addEventListener(‘click’, function(e){
    // Settings = require(‘ui/common/Settings.js’);
    // var settingsWindow = new Settings();

    if (e.source.settingsOpen) {
    g.navBar.animate({
    left: 0,
    curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
    });
    e.source.settingsOpen = false;
    } else {
    g.navBar.animate({
    left: 160,
    curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
    });
    e.source.settingsOpen = true;
    }
    });
    self.leftNavButton = leftNavBarButton;

    // RIGHT button
    var rightNavBarButton = Ti.UI.createButton({
    color:’#FFF’,
    title:”Start”,
    font:{fontFamily:’HelveticaNeue’, fontSize:11}
    });
    // rightNavBarButton.addEventListener(‘click’, function(){
    //
    // });
    self.rightNavButton = rightNavBarButton;

    // table data… USER INFO. Create custom rows with this function
    function customRow(_label, _title, _hasDetail, _header) {
    var aRow = Ti.UI.createTableViewRow({
    height: 44
    });

    // Alignment and font data
    var _top = 14;
    var fontdata = {fontFamily:’HelveticaNeue’, fontsize:11, fontweight:’light’};

    // name label
    var nameLabel = Ti.UI.createLabel({
    text:_label,
    width:’auto’,
    textAlign:’left’,
    top: _top,
    left: 15,
    font:fontdata
    });
    // data label
    var dataLabel = Ti.UI.createLabel({
    text:((_title.length) ? _title : ‘-’),
    width:’180′,
    textAlign:’left’,
    top:_top,
    left:80,
    font:fontdata
    });

    aRow.add(nameLabel);
    aRow.add(dataLabel);
    aRow.hasDetail = _hasDetail;
    if (_header != null) {
    aRow.header = _header;
    }
    return aRow;
    }

    var userInfo = [
    customRow('name', g.userName, false, ''),
    customRow('date', g.date, true, null),
    customRow('time', g.time, true, null),
    customRow('site', g.site, true, ''),
    customRow('busines', g.business, true, null),
    customRow('partner', g.partner, true, null)
    ];
    // grouped TABLE
    var table = Ti.UI.createTableView({
    style: Titanium.UI.iPhone.TableViewStyle.GROUPED
    });
    table.setData(userInfo);
    self.add(table);

    return self;
    }

    module.exports = HeaderInfo;