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.