Retrieve Your cordova app’s localStorage Data Directly From iOS Device

Use Case

If WiFi fails, your ajax call fails and your backup re-submit code fails, you may need a last ditch solution of physically grabbing your form data from inside your PhoneGap app, off of an individual iOS device.

Prerequisites

Make sure the app is coded to save data locally using HTML5 localStorage API.

Once the localStorage functionality is tested and working, and you have your PhoneGamp / cordova app running on an iPad, you can use PhoneView to access the file system on your the iPad.

Install DB Browser for SQLite on your Mac. Under the hood, cordova saves your localStorage data as a simple SQLite database.

Retrieve the Data

  1. Connect your device to your Mac and open PhoneView Demo
  2. Go to Apps in the main nav on the left.
  3. Click on Settings in the top nav icons bar and you’ll see this dialog window:
    sc1
  4. Check the “Show Entire Disk in Disk Mode” and “Show All Apps in Apps Mode (Developers Only)” boxes. You’ll get a warning confirmation popup, which you should accept by clicking OK.
  5. You’ll now see your custom cordova app listed under Apps:
    screen-shot-2016-10-25-at-4-10-00-pm

  6. The folders you want is yourApp/Library/Caches/ and yourApp/Documents/Backups/. The file should be called something like “file__0.localstorage” in /Library/Caches or “localstorage.appdata.db” in /Documents/Backups. It’s essentially a SQLite file, and can be opened using an app that lets you view SQLite files.
  7. To open it, you need to first copy the Library folder to your Mac.
  8. Click the Library folder to to select it
  9. Click on Copy from iPhone button in the top icons navbar
  10. Choose a location to save it on your machine.
  11. Launch DB Browser for SQLite
  12. Open your recently copied “file__0.localstorage” or “localstorage.appdata.db” files from your Mac in DB Browser for SQLite
  13. Click on the Browse Data tab towards the top of the interface
  14. You should see your localStorage Key / Value pairs listed as a SQL table:
  15. If you stored your Values as Arrays, your actual data may be stored as a BLOB data type and not plain text
  16. Select your BLOB value in the table and look at the right side of the interface, under Edit Database Cell
  17. Make sure the Mode is set to “Binary”
  18. You should be able to see your data with very bad kerning
  19. Click the “Export” button above the cell content area
  20. Save your file as .txt and open the .txt in any text editor, like SublimeText, to access your data as plain text.

Meraki MDM and iOS 10.0.2 – “App Already Scheduled For Management” and “Unable to install right now” Error Messages

The Symptoms

The issues this time were very similar, almost identical to what I’ve written about here. Meraki’s individual device page Activity Logs would show us the following error over and over again: “App… Already Scheduled For Management”. On the remote end, in a different city, an on-site event lead would be trying to install and getting the vague “Unable to install right now” popup message repeatedly.

The Workaround

We’ve tried everything from uninstalling all provisioning profiles from our iPads (via Settings > General > Profiles) to sending uninstall/reinstall signals from the Meraki dashboard, both from the individual device page and from the individual app page.

We eventually worked past our “Unable to install right now” message but then got stuck with grey stub icons for our apps that said “Waiting…” under the grey image.

Nothing worked until we did a full factory reset of each iPad, re-set up our Apple ID via the Settings app, re-downloaded the free Meraki app, re-registered with Meraki using our network ID.

Good times were had by all. Dinner was eventually served. Experiential / Live Events marketing + wireless technology = be prepared to have your tech guys on standby.

Notes on Cisco’s Meraki Systems Manager as an MDM Solution

A few of the devs on my team recently got a chance to try out the free version of Meraki’s Systems Manager. In this case, I’m using MDM as an acronym for Mobile Device Management. We used the system on and off for most of 2015 to distribute some apps published using an enterprise provisioning profile for iOS.

Use cases

Our usual scenario involved a series of iPads, often 10 – 30 of them. They would be sent out to an event space on a convention floor, a mall booth, an outdoor event space (like Six Flags). Sometimes the devices would be used for user registration, running on a local network inside a stadium. One project involved a bunch set up outside a traveling food truck, that drove from town to town. A few projects required sweeps registration or mailing list sign up of some sort. One large project involved multiple Elo Touch Screens that ran Windows Pro and traveled between mostly outdoor events. Since most of our events involved using Meraki with a series of iPads, I’ll use the .ipa app upload process as the example going forward.

Overall impression

Overall, if your app is under 30MB in file size, Meraki is a great solution. Just set up your account, go to

  • Systems Manager > MDM > Apps
  • select “Add New” in the upper right corner of the page and “iOS enterprise app” from the dropdown.
  • Under Location, choose “Upload an ipa” from the dropdown and follow the given options, as needed. Click Save Changes and you’re set.
  • Add devices (Systems manager > MDM > add devices) and use tags to automatically install your app on the appropriate device
  • Once the app’s live and installed, you can update using the same App page (Systems Manager > MDM > Apps, click your app name)

Some issues we came up against

  • The Meraki dashboard doesn’t seem to allow you to search “Clients” (Systems manager > Monitor > Clients, what the system calls the devices you’ve registered) by convenient terms like serial number or device name. Sometimes I have a lot of devices with a given tag and I need to make sure a specific device is in that list. Being able to search by device serial number would save me time.
  • Enterprise apps larger than 30MB in size are often problematic to update. It often took multiple attempts over a few days to get the update to work, even after on-site connectivity was good for other purposes, such as playing back a streaming video off of sites like CNN.com.
  • On-site firewall at YMCA branches (using YMCA WiFi) were blocking remote app updates.
  • Spotty venue WiFi (read: convention floor WiFi) and sometimes spotty MiFi connections are an issue if your app requires an update or needs to send more than just text data. App updates for apps bigger than a few MB, required taking the device to another location with strong, consistent WiFi.
  • When you’re stuck on an issue, such as an app just won’t install, no matter what you do, there’s not much google-able info out there. Stackoverflow is very helpful to keep you moving, past bugs and roadblocks, but not with the free version of Meraki’s System’s Manager. We haven’t yet had the need to contact Meraki support (given our previous experience with trying to reach out to support with free products, we pursued the issues on our own, so far successfully).

Some conclusions

I’d highly recommend Meraki for smaller sized apps, especially ones that are not likely to require an app update during your short term event’s duration. For longer term installations, larger apps, be prepared to budget for and plan for on-site travel for one of your developers, technologists or IT guys to help with initial installs or especially problematic app updates.

This has less to do with MDM, and more with network connectivity, but your MDM will largely depend on how good your connection is. Seriously consider / advise your client to purchase a data plan for the device, rather than relying on WiFi alone. Find out ahead of time, which mobile service has a reputation for working best in your target location. Don’t use Verizon, if AT&T is generally known to have better service in your event’s target area. If your devices are PCs, by all means, run hard lines (Ethernet) and bypass wireless issues all together. Just keep in mind, the longer those Ethernet cables, the lower the signal tends to be.

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 & 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: 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 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();	
}
 

Unity3D for iOS: “Apple LLVM Compiler 3.0 Error”… “iPhone_target_Prefix.pch’ has been modified since the precompiled header was built”

The Error

I upgraded to the latest minor version of Unity 3.5, specifically 3.5.5f. All was fine until I tried to create an Archive so I could publish an .ipa file from Xcode. Once I hit Product > Archive >, I got Build Failed with this error:

Apple LLVM Compiler 3.0 Error:
fatal error: file '/YourPathToProject/YourProjectName/Classes/iPhone_target_Prefix.pch' has been modified since the precompiled header was built

The Solution

After trying to Clean the project & a few other things, nothing worked. Finally, found this stackoverflow solution that fixed it. The Library folder path for the folder that had to be deleted was under
/YourUserName/Library/Developer/Xcode/DerivedData/{project name + gobly-gook}.

Encouraging Stats for Unity3D as a Mobile Games Dev Platform but There’s Still a Reality Check for Non-Indie Developers

Interesting statistics from Game Developer magazine (May 2012), via Gamasutra:

…Game Developer magazine has released the results of its first-ever mobile and social developer technology survey in the May 2012 issue….

Unity topped the list of mobile game engines, with 53.1% of developers reporting using Unity compared to 39.8% using a custom engine., 17.7% using Cocos2D, 5.3% using Marmalade, and 5.3% using Corona (this question wasn’t exclusive, so the percentage count adds up to more than 100%).

I’m guessing most of the 53% are indie developers for now.

At the moment (Fri, Aug 24, 2012), I did a search for “Unity3D” on Indeed.com in all of the US, and got less than 100 job postings, many of them only mentioned Unity3D as a “nice to have”. Compared that with 8,828 jobs that mention HTML5, 12,567 for Flash (yes, still!), 2,087 for Actionscript (even after “Flash found dead in seedy San Jose motel room“) on the same date in all of the US.

I hope more Tech Directors & teams hear about Unity soon & pick it as their dev tool for mobile games & beyond. I like working in this game engine, so it’d be nice to have more job opportunities for it in more places.