iOS App Store Distribution Process – Random Notes for Titanium 2.x, Xcode 4.3, OS X 10.7

Creating a Distribution .ipa Using a Client’s Certificate & Profile WITHOUT Client’s iTunesConnect login credentials

Application Loader on OS X 10.7

  • In Application Loader, choose Deliver Your App for App Store distribution.
  • On OSX 10.7, Application Loader won’t come up through Spotlight. Go to /Applications/Xcode/Contents/Applications/ to find Application Loader.

General Initial Steps

To send to App Store you need an account w/ Agent level access (so Distribution tab shows up under Certificates in the portal) and these:

1. Distribution Certificate
2. Distribution Provisioning Profile

Apple’s details on how to create the Distribution Certificate using your Keychain Access on a Mac. At the end of this process you’ll have a .certSigningRequest file, which you then need to submit, via the iOS Provisioning Portal. Once it’s approved, you can download the certificate as a .cert file, double click it on your machine to install and then use KeyChain Access to save it in the .p12 file format.

Once the Distribution Certificate is ready, make a Distribution Provisioning Profile.

Titanium for iOS: Quick Profiling Example Using Xcode’s Instruments

One thing that, for now, sucks about Unity3D for iOS is that there seems to be no easy way to use a Profiler on your Unity generated .ipa file (am I wrong?). There’s progress on the Android side with this with official profiling support in Unity 4.0. While you can’t easily do 3D gaming using Titanium, you can use Xcode’s Instruments to profile your code to make sure there’re no memory leaks.

Titanium’s docs have a video on how to profile using Instruments. Here’s a simplified example .js file loaded into app.js, using the Single View application template in Titanium 2.x:

module.exports = function ApplicationWindow() {
	
	//load component dependencies
	var FirstPage = require('/ui/FirstPageView');
	var currentPg;

    //create component instance
	var self = Ti.UI.createWindow({
		layout: 'composite',
		width: Ti.UI.FILL,
		height: Ti.UI.FILL,
		backgroundColor:'#ffffff',
		navBarHidden:true,
		exitOnClose:true
	});

    currentPg = new FirstView(self);
    self.add(currentPg); 
   
    //will result in a memory leak that will show up as forever incrementing TiUIViewProxy instances in Instruments > Leaks under the "# Living" column 
	self.addEventListener('showSecondPage', function(evt){
		var SecondView = require('/ui/SecondPageView');
		self.remove(currentPg);
		currentPg = null;
		currentPg = new SecondView();		
		self.add(currentPg);	
	});

    return self;
}   

The above code would result in numerous leaks related to various subviews that exist whatever view is currently represented by the currentPg variable (FirstPageView or SecondPageView, depending on which event was fired). In my case, I profiled my app via Xcode’s Instruments, as directed here, and saw the TiUIViewProxy object incrementing forever, each time I went back to a particular page. It never moved from the
“# Living” to the “# Transitory” column.

The fix is fairly simple. I had to run a loop to setting all of currentPg’s subviews to null, “currentPg.children[i] = null”:

...
   //leak free version: 
	self.addEventListener('showSecondPageVersion2', function(evt){
		
           //clean up all subviews of currPage view (or app will get slower the longer you use it)		
		for(var i=0, l=currentPg.children.length; i<l; i++) {
			currentPg.children[i] = null;
		}

           var SecondView = require('/ui/SecondPageView');
		self.remove(currPage);
		currentPg = null;
		currentPg = new SecondView();		
		self.add(currentPg);	
	});
	...

You can improve the above code by adding a destroy() method to both FirstPageView.js and SecondPageViewjs and instead of using a loop in ApplicationWindow’s event listener just do currentPg.destroy().

Titanium & Unity3D for iOS: Notes on Flurry Analytics – Parameters Take a Week to Show Up in Reports

I used an open source Flurry module developed by SoftGravity. It was easy to implement and seems to be working well. One potentially confusing thing with Flurry’s dashboard is that it’s not immediately clear how long it takes data to populate the various charts & reports.

“You currently have no event parameters to track.”

For example, I can see my parameter Name / Value pairs Event Logs. The little pie chart icon for Event Parameters shows up next to my events that have parameters. However, when I try to look at any of the reports for parameters that show up in the Event Logs I constantly get this message with no additional details: “You currently have no event parameters to track.” None of the FAQ pages on flurry.com mention this situation.

After installing the module, I’m calling logEvent, after initiating flurry w/ my API KEY, like so:

//in app.js: 
var flurry = require('sg.flurry');
	flurry.secureTransport(true); //use https to send request to make them more safe
	flurry.logUncaughtExceptions(true); //logs exception in objective-c code
	flurry.startSession('YOUR_API_KEY_HERE'); 
...

//in a content .js file:
flurry.logEvent( "My Footer Event" , {myfooter_link: 'Contact Us'} ); 

48 hrs after I created my application in the Flurry admin dashboard, this problem still persisted. At the time I was using Titanium SDK 2.1.3.GA and Titanium Studio, build: 2.1.2.201208301612.

Give it at least a week

5 days after I created the app in Flurry’s dashboard and started posting data to the Event Logs, my Event Parameters finally started showing up in the reports and the “You currently have no event parameters to track” message went away.

I first tried Flurry a while back with a test Unity3D project, using Prime31’s great Flurry for iOS plugin. It seemed to work well, but at the time, I didn’t pay much attention to the Event Parameters and how long they take to show up in the reports. Looking at my old Unity test data in Flurry’s dashboard, they’re certainly there now.

Titanium for iOS: Changing System Date breaks the Simulator Build Process – “fatal error: file” ‘_Prefix.pch’ “has been modified since the precompiled header was built”

Got this “fatal error” with Titanium Studio, build: 2.1.2.201208301612 and Titanium SDK version 2.1.3 after I had to change my OS X system Date via Date & Time Preferences manually while trying to test badge activation functionality that involved keeping track of an activity being performed several consecutive days in a row. All of the usual solutions, like Clean project, delete DerivedData, delete /build/ folder didn’t work this time.
Changing the system Date back to the correct one, as implied here, didn’t help either.

[INFO] One moment, building ...
[INFO] Titanium SDK version: 2.1.3 (10/02/12 16:16 15997d0)
[INFO] iPhone Device family: universal
[INFO] iPhone SDK version: 5.1
[INFO] iPhone simulated device: ipad
[INFO] Minimum iOS version: 4.0 Linked iOS Version 5.1
[INFO] Compiling localization files
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] fatal error: file '/Users/yourUserName/Desktop/SVN/CompanyName/trunk/yourProject/build/iphone/yourProjectName_Prefix.pch' has been modified since the precompiled header was built
[ERROR] 
[ERROR] Error: Traceback (most recent call last):
  File "/Users/yourUserName/Library/Application Support/Titanium/mobilesdk/osx/2.1.3.GA/iphone/builder.py", line 1325, in main
    execute_xcode("iphonesimulator%s" % link_version,["GCC_PREPROCESSOR_DEFINITIONS=__LOG__ID__=%s DEPLOYTYPE=development TI_DEVELOPMENT=1 DEBUG=1 TI_VERSION=%s %s %s" % (log_id,sdk_version,debugstr,kroll_coverage)],False)
  File "/Users/yourUserName/Library/Application Support/Titanium/mobilesdk/osx/2.1.3.GA/iphone/builder.py", line 1231, in execute_xcode
    output = run.run(args,False,False,o)
  File "/Users/yourUserName/Library/Application Support/Titanium/mobilesdk/osx/2.1.3.GA/iphone/run.py", line 41, in run
    sys.exit(rc)
SystemExit: 65

Lame workaround

Let Titanium IDE generate the build folder w/ the Xcode project. Launch Xcode by double clicking the yourProjectName.xcodeproj and Build from there.

What’s even worse, is that building to a device works out of Titanium IDE in this case but not from Xcode 4.3.3. In XCode, I get this error: “Apple Mach-O Linker error… linker command failed with exit code 1 (use -v to see invocation)”

Solution

My buddy Charles, suggested this approach:

  1. Instead of Run > iPad Simulator, try Debug > iPad Simulator. This should get the Simulator out of its stupor and running again.
  2. Go to the Simulator’s equivalent of DerivedData and delete all project folders (titled with what looks like random strings):
    /Users/yourUserName/Library/Application Support/iPhone Simulator/5.1
  3. Go to Run > Run Configurations, select your simulator configuration, like Titanium iOS Simulator > Titanium iPad Simulator.
  4. Check the box next to “Re-build project on launch” and hit Apply

The problem re-appeared a few times again on the same day after the above fix took care of it. Going back into Run Configurations and unchecking and then re-checking “Re-build project on launch” fixed it each time. UPDATE, 12.07.2012: the weird part was that sometimes just doing the Run Configuration checkbox thing didn’t always work when the problem re-appeared, so I had to run through all the steps above again.

Titanium for iOS: POST a JSON Object to a Web Service

Assume I’m using the boilerplate Ti.Network.createHTTPClient call with an “onload” & “onerror”.

...
    var xhr = Ti.Network.createHTTPClient({ ... });    
 
    myObj = { mydata:  
        { myitems: [ 
                { mainID:7452, someOption:87, theDate:'2012-10-23' },
                { mainID:2342, someOption:27, theDate:'2011-06-03' },
                { mainID:1914, someOption:43, theDate:'2012-02-14' }
            ]
        }
    };
...
    var strMyObj = JSON.stringify(myObj);
	Ti.API.info("strMyObj = " + strMyObj);

    xhr.open("POST", "http://www.yourPathHere.com/yourWebServiceName");
	xhr.setRequestHeader('Accept','application/json'); 
	xhr.setRequestHeader('Content-Type','application/json'); 				
	xhr.send( strMyObj ); 
...

Works in CURL, not in Titanium

I ran into this issue first. The following CURL command worked in Terminal:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d "{mydata:{myitems:[{mainID:7452, someOption:87, theDate:'2012-10-23'}]}}"
 http://www.yourPathHere.com/yourWebServiceName

In Titanium I kept getting HTTP response status “403” because I made stupid mistake, using “PUT” instead of “POST” in xhr.open(). Good times.
The “-i” stands for “include”, “-H” specifies an HTTP header. The “-X” in front of “POST” tells curl to use something other than “GET”. “-d” stands for data to include in your HTTP POST, mimicking an HTML form request. Here’s a list of CURL flags.

JSON.stringify

After fixing that, I got HTTP response status “500”, by just doing “xhr.send(myObj);”.

Thanks to some debugging by my backend dev Wes, it turned out the service simply wanted the data formatted as a String. He tried, taking the quotes off the {mydata…} line in the CURL command and got the same status 500 error in Terminal and via Chrome’s console:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d {mydata:{myitems:[{mainID:7452, someOption:87, theDate:'2012-10-23'}]}} 
 http://www.yourPathHere.com/yourWebServiceName

curl: (6) Could not resolve host: someOption:87,; nodename nor servname provided, or not known
curl: (3) [globbing] unmatched close brace/bracket at pos 19
HTTP/1.1 500 Internal Server Error
Date: Fri, 05 Dec 2012 18:58:48 GMT
Server:...
jsonerror: true
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 1761
{"Message":"Invalid object passed in, \u0027:\u0027 or \u0027}\u0027 expected. (31): {mydata:{entries:[{mainID:7452,","StackTrace":"   at..."}

Titanium for iOS: Faking a Lightbox style Modal Window in your App

This is an example of how to do a Lightbox style modal window (a View posting as a Window) in a Titanium 2.x SDK iOS app.

Why bother?

Sometimes it’s useful for in app messaging, when you want to avoid opening an actual Window via Ti.UI.createWindow and stay in the same execution context as recommended by the best practices section of Titanium docs. You can also use this approach when the look & feel of the default iOS alert window just isn’t your style.

Sample usage

Here’s how you’d use it:

   var FakeModalWindow = require('/ui/FakeModalWindow');	
  
    var someText = "OK, some new text. Blah Blah Blah Blah Blah . \n\nThis new one here. \n\nYou new copy. \n\n Some More stuff and stuff.."; 
  	var fmw = new FakeModalWindow();
	self.add(fmw); 		
		
	//to change the copy in the fmw's TextArea: 
	fmw.fireEvent('setCopy_fake_modal_win', {newCopy:someText}); 
			
	self.addEventListener('close_fake_modal_win', function(e){		
		fmw.hide();		
	});
	
	self.addEventListener('open_fake_modal_win', function(e){		
		fmw.show();		
	});	

Source

Here’s the actual FakeModalWindow.js module code:


module.exports = function FakeModalWindow() {

	var theCopy = "Lorem Headline" + 
		           "\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "+
				   "\n\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + 
				   "\n\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +				  
				   "\n\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "; 
	}
	
	// some variables we'll need
	var OS = Ti.Platform.osname;	
	var SLASH = Ti.Filesystem.separator;
	var imgPath = SLASH + 'images'; 	
	
	//create component instance
	var self = Ti.UI.createView({
		width: Ti.UI.FILL,
		height: Ti.UI.FILL,
		backgroundColor:'#65000000',
		zIndex : '50',
	})	
	
	/**		
	 * Main container w/ white background & rounded corners
	 */	
	var content_win = Ti.UI.createView({
		backgroundColor:'#ffffff',	
		layout: 'vertical',
		width: (OS==='ipad') ? '542dip' : '270dip',	//'542dip', //
		height: Ti.UI.SIZE, //'526dip' (OS==='ipad') ? '526dip' : '15dip',	
		borderRadius: (OS==='ipad') ? 6 : 2	
	});	
	
	/**		
	 * Horizontal container 1
	 */	
	var title_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#FF00FF',						
		//borderWidth: 1				
	});	
	
	/**
	 * Horizontal container 2
	 */	
	var content_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#FF0000',
		//borderWidth: 1	
	});		
	
	/**
	 * Horizontal container 3
	 */	
	var footer_bar = Ti.UI.createView({
		layout: 'horizontal',
		height: (OS==='ipad') ? '20dip' : '10dip',
		//borderColor: '#FF0000',
		//borderWidth: 1	
	});			
		
	/**
	 * Begin content pieces  
	 */
	var orange_bar = Ti.UI.createView({		
		backgroundColor: '#f37416',	
		width: Ti.UI.FILL, 
		height: (OS==='ipad') ? '57dip' : '26dip',	
		borderRadius: (OS==='ipad') ? 6 : 2,
		borderColor: '#white',
		borderWidth: (OS==='ipad') ? 5 : 2,
	});			
	
	var close_button = Ti.UI.createView({		
		backgroundImage: '/images/shared/btn_close.png',	
		left: (OS==='ipad') ? '485dip' : '5dip',	 
		width: (OS==='ipad') ? '37dip' : '18dip', 
		height: (OS==='ipad') ? '37dip' : '18dip' 	
	});	
		
	var body_copy = Ti.UI.createTextArea({
		//borderWidth: 2,
	    //borderColor: '#CCFF33',	    
		value: theCopy,		
		editable: false,
		textAlign: 'left',
		top: (OS==='ipad') ? '15dip' : '7dip',
		left: (OS==='ipad') ? '20dip' : '10dip',
		color: '#000000',		
		font: { fontSize: (OS==='ipad') ?'20dip' : '12dip' },
		width: Ti.UI.FILL,
		height: Ti.UI.SIZE 				
	});			
	content_win.updateLayout({height:Ti.UI.SIZE});
		
	orange_bar.add(close_button);			
	title_bar.add(orange_bar);		
	content_bar.add(body_copy);			
	contentWin.add(title_bar);	
	contentWin.add(content_bar);	
	contentWin.add(footer_bar);
	self.add(content_win);

	/**
	 * Event listeners 
	 */
  	self.addEventListener('setCopy_fake_modal_win', function(e){			
		body_copy.setValue(e.newCopy);				
		body_copy.updateLayout( {height: Ti.UI.SIZE } );				 		
	});	
	
	self.addEventListener('click', function(e){  // if the user interacts anywhere, close the modal window
		self.fireEvent('close_fake_modal_win');	
	});
		
	return self;
}  

Titanium for iOS: Manipulating Width, Height, Left, Top of Views in the iOS Simulator Using the Debug View

I learned this useful technique from Charles Loflin, a brilliant developer I’ve had a chance to work with recently. This approach lets you reduce the amount of guess work involved in trying to set the x, y positioning and width, height of elements on screen. Instead of having to Run after every time you make a change to the left, top or width properties of a View, you can just Run once, figure out the correct values with live preview in the Simulator and update your code once.

Using Eclipe IDE’s DEBUG View to set width, height of a Ti.UI.View via the Simulator

The steps involved:

  1. add footr.fireEvent(‘MYDEBUG_Event’) in ApplicationWindow.js,right after you declare ‘myv’ as an instance of MyNestedView.js.
  2. add listener for ‘MYDEBUG_Event’ in MyNestedView.js after you declare the visual subview you’re trying to position (var headerImage).
  3. set break point for a Ti.API.info line inside the ‘MYDEBUG_Event’ listener – this will give you access to module’s variable (‘var headerImage’) in the Variables View of the Debug perspective (Window > Open Perspective > Debug).
  4. Select the class member whose width/height you’re trying to manipulate in the Variables View. In this case, select ‘headerImage’ var and drill down to it’s ‘width’ property. Select it ‘width’. It’s value should show up in the bottom input text panel of the Variables View. Change the value to something new (don’t use quotes, it’ll add it for you) and hit Cmd+S to Assign Value. You should see the view’s width change in the Simulator.

Here’s the fireEvent call for Step 1:

//Application Window Component Constructor
module.exports = function ApplicationWindow() {
	
	//load component dependencies	
	var MyNestedView = require('ui/MyNestedView');	
    ... 
	var myv = new MyNestedView();
	self.add(myv);
	
	//FOR DEBUG ONLY: 
	myv.fireEvent('MYDEBUG_Event');
    ...
    return self;
}

ApplicationWindow.js is loaded in from app.js in the same way the Single Window application template does in the Titanium IDE.

Here’s the listener call for Step 2:

module.exports = function MyNestedView() {
	
	var OS = Ti.Platform.osname;
	var SLASH = Ti.Filesystem.separator;
	var imgPath = SLASH + 'images';	
					
	//create component instance
	var self = Ti.UI.createView({
		bottom: (OS==='ipad') ? '10dip' : '20dip', 
		//backgroundColor: '#FF00FF', //for debug only
		width: Ti.UI.FILL,
		height: Ti.UI.SIZE //keep the view only as tall as needed to fit the text, otherwise it'll cover up links below 		
	});

	var headerImage = Ti.UI.createView({			
		left:'11dip', 
		width: (OS==='ipad') ? '751' : '314dip',   // 314/751 = x/100 --> 31400 = 751x --> x = 31400/751 --> x = 41.81 ~ 42%
		height: (OS==='ipad') ? '135dip' : '57dip', // 42% of 135dip = 56.7 ~ 57		
		backgroundImage: imgPath + SLASH + 'shared' + SLASH + 'my_header_image.png' 
	});		
	self.add(headerImage);

                //FOR DEBUG ONLY: 
		self.addEventListener('MYDEBUG_Event', function(){ 		
			Ti.API.info("break point point in MyNestedView"); //set breakpoint on this line
		});
	
...

    return self;
}

The fireEvent and its associated listener allow us to access the scope of MyNestedView module after MyNestedView’s “return self;” fires. Once we’re in this scope, we have access to it’s “var headerImage” member.

Titanium SDK for iOS: ScrollableView for Displaying Several Pages of Photo Thumbnails

Here’s a quick example of one way to dynamically populate a ScrollableView using Titanium SDK 2.x. I started with an example from this pastebin sample.

The below code displays a horizontally ScrollableView with default iOS page controls. Each page displays 6 photo thumbnails, while the last page displays the remainder (less than 6). This should work on Android, but I haven’t had time to test it yet (famous last words, I know).

SideScrollPagedView() is a CommonJS module (one of the recommended ways to organize code in Titanium).

module.exports = function SideScrollPagedView() {
	
    var OS = Ti.Platform.osname;	

	// in real life this might come from a database or web service or be dynamically populated from a local directory 
	var data = [ { PhotoID: 1, Photo_Title: 'my first photo', Thumb_Path: 'photothumb_1.jpg'},
				 { PhotoID: 2, Photo_Title: 'my second photo', Thumb_Path: 'photothumb_2.jpg'},  		
                 { PhotoID: 3, Photo_Title: 'my third photo', Thumb_Path: 'photothumb_3.jpg'}, 
                 { PhotoID: 4, Photo_Title: 'my fourth photo', Thumb_Path: 'photothumb_4.jpg' }, 
                 { PhotoID: 5, Photo_Title: 'my fifth photo', Thumb_Path: 'photothumb_5.jpg'}, 
                 { PhotoID: 6, Photo_Title: 'my sixth photo', Thumb_Path: 'photothumb_6.jpg'}, 
                 { PhotoID: 7, Photo_Title: 'yeah, 7th photo', Thumb_Path: 'photothumb_7.jpg'}, 
                 { PhotoID: 8, Photo_Title: 'ok, 8th photo', Thumb_Path: 'photothumb_8.jpg'}, 	
                 { PhotoID: 9, Photo_Title: 'this is the 9th photo', Thumb_Path: 'photothumb_9.jpg'}, 
                 { PhotoID: 10, Photo_Title: 'photo number 10', Thumb_Path: 'photothumb_10.jpg'}, 
                 { PhotoID: 11, Photo_Title: 'photo number 11', Thumb_Path: 'photothumb_11.jpg'}, 
                 { PhotoID: 12, Photo_Title: 'photo number 12', Thumb_Path: 'photothumb_12.jpg'},
                 { PhotoID: 13, Photo_Title: 'photo number 13', Thumb_Path: 'photothumb_13.jpg'},
                 { PhotoID: 14, Photo_Title: 'photo number 14', Thumb_Path: 'photothumb_14.jpg'},
                 { PhotoID: 15, Photo_Title: 'photo number 15', Thumb_Path: 'photothumb_15.jpg'},
                 { PhotoID: 16, Photo_Title: 'photo number 16', Thumb_Path: 'photothumb_16.jpg'} ];
	
	//main view 		
	var self = Ti.UI.createView({
		layout: 'vertical',
		width: '100%',
		height: Ti.UI.SIZE
	});			
	
	//you can stack another one of these above or below this one	
	var mainContentBar = Ti.UI.createView({
		layout: 'horizontal',
		height: Ti.UI.SIZE,
		//borderColor: '#CCCCCC',
		//borderRadius: 2
	});
	self.add(mainContentBar);				
   
	var views = []; //array that holds a collection of "pages" (views) to be assigned to ScrollableView's .view property
    var gameData = data; //copy of 'data' array, lets us slice it up per page, while keeping original list in tact	
	var itemsPerPage =  (OS==='ipad') ? 6 : 4; //number of photos to display per page inside ScrollableView 
	var gutterSpace = (OS==='ipad') ? 16 : 9;  
    var halfOfItems = itemsPerPage * 0.5;
	var buttonWidth = (OS==='ipad') ? 204 : 132; 
	var buttonHeight = (OS==='ipad') ? 152 : 99; 		
	var scrollablePgW = (OS==='ipad') ? '644dip' : '273dip'; 
	var scrollablePgH = (OS==='ipad') ? '360dip' : '230dip'; 	
	var numPages = Math.ceil(data.length/itemsPerPage);	//numPages determined by number of times TotalFavorites / itemsPerPage, rounded up for whole pages		
	var itemsOnLastPage = data.length % itemsPerPage;

	var lastInRow1 = (OS==='ipad') ? 2 : 1;
	var lastInRow2 = (OS==='ipad') ? 5 : 3;
	var sidePadding = true;		
 
	for ( var i = 0 ; i < numPages; i++ )
	{
	      var view = createView(views.length);
    		
          //modify itemsPerPage for last page	           
      	  if(i == (numPages-1)) { itemsPerPage = itemsOnLastPage; }
      	 
      	  var perPageData = gameData.splice(0, itemsPerPage);  
       	 
		  //for each set of itemsPerPage (6 or less), add an icon subview to view 	
  		  for(var h = 0 ; h < itemsPerPage; h++) 
  		  {
  		      //remove gutter space from last icon in each row to prevent the parent view (w/ layout:'horizontal') from forcing the next row too early  		  	 
  			 ( h === lastInRow1 || h === lastInRow2 ) ? sidePadding = false : sidePadding = true;  
  			 
  	   	  	 var subview = createSubView( sidePadding );	      
  	   	  	 var item = perPageData[h]; 
  	   	  	   	   	  	 
  	   	  	 var iconItem = Ti.UI.createView({
				backgroundImage: '/images/photos/thumbs/' + item['Thumb_Path'],
				layout : 'composite',
				width : buttonWidth,
				height : buttonHeight,
				left : 0, 
				top : 0,
				visible : true,
				photoData : item //can be used to later grab property values like photoData.Photo_Title
			});			  	   	  	
  	   	  	iconItem.addEventListener('singletap', function(evt) { 				
				this.fireEvent('scrollItem_Clicked', {data : this.photoData	});				
			}); 			

  	   	  	//add additional subviews to iconItem here as needed

  	   	  	subview.add(iconItem);
  	   	  	 			
  			view.add(subview);
  		  }	      
      	   	  
	      views.push(view);
	}

	function createSubView(xval, yval)
	{
		var rightPadding = (OS==='ipad') ? '16dip' : '8dip';
		var btmPadding = (OS==='ipad') ? '17dip' : '8dip'; 
		if(!sidePadNum) {
		     rightPadding = 0;
		}
		
		var view = Ti.UI.createView({			
			width: buttonWidth, 
			height: buttonHeight, 			
			bottom: btmPadding, 
			right: rightPadding,
			//borderColor: '#FF00FF'			
		});		
		
		return view;
	}	

	function createView(i)
	{
		var view = Ti.UI.createView({
			backgroundColor: '#FFFFFF',
			text: i, 
			layout: 'horizontal'
		});
		return view;
	}	
	 	 
	var scrollView = Titanium.UI.createScrollableView({
			top: (OS==='ipad') ? '25dip' : '15dip',
			left: (OS==='ipad') ? '50dip' : '20dip',
			width: scrollablePgW, 
			height: scrollablePgH, 
			//borderColor: '#AACC00', //for debugging		
			views:views,
		  	showPagingControl:true,		
		  	pagingControlColor: '#666666',
	        currentPage:0
	});	 
	
	mainContentBar.add(scrollView);	        	          	
	
	return self;
}

This assumes that the actual thumbnail images are stored under /Resources/images/photos/thumbs/.

ScrollableView and clickable items – avoiding click/swipe conflicts

Notice how the “iconItem.addEventListener(‘singletap’, function(evt){})” is listening for a ‘singletap’ event and NOT a ‘click’ event. I tried using ‘click’ first and ran into the problem of the photo icons being accidentally clicked while I’m trying to swipe (scroll) the ScrollableView.

Usage

To use the above module inside another file, you can do something like this, depending on what you’re doing:

//Application Window Component Constructor
module.exports = function ApplicationWindow() {
     ...	
	//create component instance
	var self = Ti.UI.createWindow({
		layout: 'composite',
		width: Ti.UI.FILL,
		height: Ti.UI.FILL,
		backgroundColor:'#ffffff',
		navBarHidden:true,
		exitOnClose:true
	})
     ...
    var SideScrollPagedView = require('/ui/SideScrollPagedView');
    var sspv = new SideScrollPagedView();
     self.add(sspv);

    return self;
}

The above code assumes that the scrollable photos component .js file is located in this location: /Resources/ui/SideScrollPagedView.js.

The above ApplicationWindow.js component would be called by app.js in the same way it is in the SingleWindow application template that comes with the Titanium 2.x IDE, like so:

//bootstrap and check dependencies
if (Ti.version &lt; 1.8 ) {
	alert('Sorry - this application template requires Titanium Mobile SDK 1.8 or later');
}
else if (Ti.Platform.osname === 'mobileweb') {
	alert('Mobile web is not yet supported by this template');
}
else {	
	...
	
	//require and open top level UI component	
	var AppWin = require('/ui/ApplicationWindow');
	var appWin = new AppWin();
	appWin.open();	
}
 

Titanium SDK: Selecting Data from Multiple Tables in SQLite with JOIN

Assumptions

For this example, I’m assuming we get our user_ID populated from a web service when the app starts.

Create your tables

There’re 3 tables, one for game text info, one for game icon graphics and one that stores a list of my favorite games.

 
CREATE TABLE MyGames (
      game_ID INTEGER PRIMARY KEY,
      game_name TEXT,
      num_players INTEGER,
      game_rules TEXT
);   

CREATE TABLE MyGameIcons (
      icon_ID INTEGER PRIMARY KEY,
      game_ID INTEGER,
      icon_ImgPth TEXT,
      detail_ImgPth TEXT
); 

CREATE TABLE MyFaveGames (
	   fave_ID INTEGER PRIMARY KEY,
	   game_ID INTEGER, 
	   user_ID INTEGER 
	)

Add some dummy data to them using SQLite Manager plugin for Firefox or a command line tool. Once you have some data, you can move on to trying out the JOIN clause.

Use a basic INSERT to save a game to the MyFaveGames table:

 
INSERT INTO MyFaveGames(user_ID, game_ID) VALUES(1823, 9)

To use this statement in Titanium SDK 2.x app, you can do this:

 
db.execute( 'INSERT INTO MyFaveGames(user_ID, game_ID) VALUES(?, ?)', varUserID, varGameID );

Assuming varUserID and varGameID are pre-populated in your app and db is defined earlier as your database connection object ( db = Ti.Database.open(myConn); )

Use SQL’s implicit JOIN clause to pull records from more than one table using one query

Pull the game info & icon graphics for games saved to My Favorite Games. Order them by the game names.

 
SELECT MyGames.game_ID, game_name, num_players, game_rules, icon_ImgPth, detail_ImgPth FROM MyGames, MyGameIcons, MyFaveGames WHERE MyFaveGames.game_ID=MyGames.game_ID AND MyFaveGames.game_ID=MyGameIcons.game_ID ORDER BY game_name

In Titanium SDK 2.x app, it can look like this:

 
db.execute('SELECT MyGames.game_ID, game_name, num_players, game_rules, icon_ImgPth, detail_ImgPth FROM MyGames, MyGameIcons, MyFaveGames WHERE MyFaveGames.game_ID=MyGames.game_ID AND MyFaveGames.game_ID=MyGameIcons.game_ID ORDER BY game_name');

The above would return a recordset, which you can loop through.

Creating a Temporary Table for Immediate Use

 
		var myDBconn = getDB();

		var temp = myDBconn.execute('CREATE TEMP VIEW MyTempTabl AS SELECT fave_ID, game_ID, user_ID FROM MyFaveGames WHERE user_ID=' + varUserID);
		var rows = myDBconn.execute('SELECT MyGames.game_ID, game_name, num_players, equip_items, fld_type, classics, fancies, game_instructns, fld_Icon, equip_Icon, composit_Icon, fave_ID, user_ID FROM MyGames LEFT OUTER JOIN MyGameIcons on MyGames.game_ID = MyGameIcons.game_ID LEFT OUTER JOIN MyTempTabl ON MyGames.game_ID = MyTempTabl.game_ID WHERE classics = "TRUE" ORDER BY game_name'); 

Grabbing Image Paths For All Images in Your App’s Local “/Documents” Directory Using Titanium SDK

This is a quick example of how to grab local images that were earlier stored as PNG’s in your app’s /Documents folder and store their paths inside an array that can later be used to populate a View like CoverFlow, etc. Note: for CoverFlow, you’ll want to first resize your images down to a smaller widthxheight or CoverFlow will crash your app. I’m using the CommonJS modules approach to app structure. When testing in the Simulator, you can find your App’s /Documents folder here: /Users/your_User_Name/Library/Application Support/iPhone Simulator/5.0/Applications/FF203495C-R93475-466B-9103-EEEF0E9687B9/Documents/. A quick way to add some sample images to your Simulator’s /Documents folder, open Safari on the Simulator, find an image, Tap & Hold until the dialog comes up asking you to “Save Image”.

function SecondView() {
	
	var self = Ti.UI.createView();	
	var imgs = [];

	/* 
	 * 
	 * The FILE system API is not fully documented on Appcelerator's website. 
	 * For a more complete list of available methods for Ti.Filesystem check the Kitchen Sink's filesystem.js 
	 * 
	 * */
    				
	function loadData() {	
						
		var dir = Ti.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory);
		var fdir = dir.getDirectoryListing();
		var numPhotos = fdir.length || 1;
		 //Documents folder is empty on 1st load post install, 
		 //so unless there's a default value, you get a blank screen... 	
		
		for(var a=0; a < numPhotos; a++ ) {
		
			var f = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,fdir[a]);
			
			if(f.exists())
			{
				var fr = f.read();		
			
				if(fr.mimeType === 'image/png'){
					Ti.API.info('photo.nativePath: ' + f.nativePath);
					imgs.push(f.nativePath); //imgs[a] = f.nativePath;				
				}			
			}						
		}
	}

	loadData();
	
	return self;
}

module.exports = SecondView;