<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Danilo Campos.blog &#187; Development</title>
	<atom:link href="http://blog.danilocampos.com/category/development/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.danilocampos.com</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Wed, 10 Aug 2011 07:33:59 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Hipmunk on Daring Fireball</title>
		<link>http://blog.danilocampos.com/2011/03/29/hipmunk-on-daring-fireball/</link>
		<comments>http://blog.danilocampos.com/2011/03/29/hipmunk-on-daring-fireball/#comments</comments>
		<pubDate>Tue, 29 Mar 2011 15:58:36 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[User Experience]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=588</guid>
		<description><![CDATA[It&#8217;s no exaggeration to say I&#8217;ve been trying to get my work on Daring Fireball since I started my iPhone development and user experience adventures three years ago. Thrilled to see it finally go down: &#8230;[T]heir custom date picker is simply brilliant. Hipmunk has a good web interface too, but it has nothing on the [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s no exaggeration to say I&#8217;ve been trying to get my work on Daring Fireball since I started my iPhone development and user experience adventures three years ago. <a href="http://daringfireball.net/linked/2011/03/29/hipmunk">Thrilled to see it finally go down</a>:</p>
<blockquote><p>&#8230;[T]heir custom date picker is simply brilliant. Hipmunk has a good web interface too, but it has nothing on the iPhone interface. This is why native apps matter.</p></blockquote>
<p>Thanks for the love, John!</p>
<p>I&#8217;m especially pleased he calls out the date picker. That was a lot of work to get right and it&#8217;s not the sort of thing you can get away with being wrong in something like flight search and still have happy users.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2011/03/29/hipmunk-on-daring-fireball/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Hipmunk for iPhone</title>
		<link>http://blog.danilocampos.com/2011/03/03/hipmunk-for-iphone/</link>
		<comments>http://blog.danilocampos.com/2011/03/03/hipmunk-for-iphone/#comments</comments>
		<pubDate>Thu, 03 Mar 2011 18:38:34 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Adventure]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Strategery]]></category>
		<category><![CDATA[User Experience]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=558</guid>
		<description><![CDATA[My awesome new job and how I built our awesome new app. Check it out! The app even won Best Design at LAUNCH 2011.]]></description>
			<content:encoded><![CDATA[<p>My <a href="http://blog.hipmunk.com/hipmunk-for-iphoneipod-touch">awesome new job</a> and <a href="http://blog.hipmunk.com/hipmunk-for-iphone-post-mortem">how I built our awesome new app</a>. Check it out!</p>
<p>The app <a href="http://blog.hipmunk.com/hipmunk-at-launch">even won Best Design at LAUNCH 2011</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2011/03/03/hipmunk-for-iphone/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Intel&#8217;s Delusions</title>
		<link>http://blog.danilocampos.com/2011/02/15/intels-delusions/</link>
		<comments>http://blog.danilocampos.com/2011/02/15/intels-delusions/#comments</comments>
		<pubDate>Wed, 16 Feb 2011 02:56:24 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Business]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Mediocrity]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=550</guid>
		<description><![CDATA[A few weeks ago I got an email from an Intel rep, trying to convince me that I should port my apps to MeeGo, their in-progress mobile platform. I chuckled at this – the thing hasn&#8217;t even shipped on anything. In what universe would it be worthwhile to invest development time in an unproven platform [...]]]></description>
			<content:encoded><![CDATA[<p>A few weeks ago I got an email from an Intel rep, trying to convince me that I should port my apps to MeeGo, their in-progress mobile platform. I chuckled at this – the thing hasn&#8217;t even shipped on anything. In what universe would it be worthwhile to invest development time in an unproven platform I&#8217;ve never even used? I didn&#8217;t give it much more thought, figuring it was email blasted and my lack of response wouldn&#8217;t be noticed.</p>
<p>I was wrong. I got another, more personal email a week later.</p>
<p>And another – right after Nokia announced they were dropping MeeGo for Windows Phone 7. This was more than I could take. With that vote of no confidence, who would be crazy enough to invest in Intel&#8217;s non-existent platform? I responded:</p>
<blockquote><p>I know you&#8217;re doing your job, but it&#8217;s not going to happen. Intel lost in mobile. Sorry. Putting my money behind horses who have a real chance. Thanks and good luck.</p></blockquote>
<p>I put it out of my mind. Later that day, though, my persistent friend gave me one last push:</p>
<blockquote><p>&#8230;If history repeats itself, it will be open architecture systems and industries that will eventually dominate. It’s only a question of when. ( I think soon.) Think of where the PC market was in the early days when there was still multiple proprietary solutions competing for the market space of the home computer user.  The IBM standard eventually dominated the larger market.</p></blockquote>
<p>Even if I bought into this (I don&#8217;t), wouldn&#8217;t Android be the horse to bet on? It&#8217;s open. It&#8217;s actually in shipping products. <em>It actually has users, right now, today. </em></p>
<blockquote><p>I believe that history will repeat itself.</p></blockquote>
<p>So, this guy, and by extension, Intel, believes that fate will grant Intel perpetual reign over all things computing, despite the fact that they can&#8217;t produce a mobile processor anyone wants to use in best-selling products? Despite the fact that they&#8217;re late to the party with their self-serving OS?  Despite the fact that MeeGo, by all accounts, <a href="http://www.pcmag.com/article2/0,2817,2380359,00.asp">kind of sucks</a>?</p>
<p>Okay, I guess history is just going to repeat itself. Because they want it to. It&#8217;s like they&#8217;ve <a href="http://www.amazon.com/Secret-Rhonda-Byrne/dp/1582701709">been reading The Secre</a><a href="http://www.amazon.com/Secret-Rhonda-Byrne/dp/1582701709">t</a> or something.</p>
<blockquote><p>This is a ground floor opportunity akin to purchasing a stock just before it goes up in value.</p></blockquote>
<p>As if my bullshit detector weren&#8217;t already burying the needle.</p>
<blockquote><p>Keep idly watching this space (open architecture mobile computing) and you will miss the train. Intel is leveraging all it’s 30 years of OEM relationships. The number of distribution channels contained in this network is going to be staggering. It’s not one store or one manufacturer.  This is the democratization of mobile computing.</p></blockquote>
<p>I think the only one who has missed the train here is Intel. They&#8217;ve been idly watching mobile while ARM quietly cleans their clock.</p>
<p>Moreover, even Google has struggled to nail down successful, paid distribution. Intel thinks it can succeed by encouraging the creation of <em>several channels</em>? It&#8217;s like they don&#8217;t even bother studying what works and why. Apple is ruling the day by getting 100 million accounts all in one database and giving the keys for one-click buying to anyone who wants to come over. <em>Several fragmented channels </em>is not the way to match their power.</p>
<blockquote><p>That’s my story and I’m sticking to it.</p></blockquote>
<p>Translated: Intel is delusional and they&#8217;re paying me to repeat their fever dreams.</p>
<blockquote><p>The mobile computer market is nearing a similar democratizing event horizon as the PC market did 25 years ago.  Throughout 2011 Intel chips will turn up in 35 tablets from 15 brands!</p></blockquote>
<p>How many units are going to be sold of all these many tablets? 15 brands and 35 tablets? Really? Why not just two, really, really good ones? Sounds like a recipe for an instantly fragmented market from a hardware perspective, too.</p>
<p>He ended by encouraging me to hop on the phone with a program manager to learn more. I haven&#8217;t taken him up on it.</p>
<p>Now, many of us have been in an room listening to marketing spin a tale of bullshit (&#8220;narrative&#8221;) to share with outsiders. Maybe Intel knows it&#8217;s full of it, right? I&#8217;m not so sure. I think decades of being the Processor King has genuinely convinced them that their success is inevitable.</p>
<p>I think they&#8217;re wrong. <a href="http://www.fernstrategy.com/2010/10/21/the-end-of-x86/">The game has changed</a>. They haven&#8217;t.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2011/02/15/intels-delusions/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Burstling</title>
		<link>http://blog.danilocampos.com/2010/08/16/burstling/</link>
		<comments>http://blog.danilocampos.com/2010/08/16/burstling/#comments</comments>
		<pubDate>Tue, 17 Aug 2010 04:25:27 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Adventure]]></category>
		<category><![CDATA[Development]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=467</guid>
		<description><![CDATA[I just released my first desktop app. It&#8217;s a small and focused tool to help you kill your procrastination and get things done. Go check it out. Developing for the desktop has been fun and an interesting change of pace from iPhone development. A full post-mortem is on the way. In the mean time, it&#8217;s [...]]]></description>
			<content:encoded><![CDATA[<p>I just released my first desktop app. It&#8217;s a small and focused tool to help you kill your procrastination and get things done. Go <a href="http://www.burstling.com">check it out</a>.</p>
<p>Developing for the desktop has been fun and an interesting change of pace from iPhone development. A full post-mortem is on the way.</p>
<p>In the mean time, it&#8217;s a great feeling to add a bit of OS X freeware to the world.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2010/08/16/burstling/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The Rediscovery of Joy</title>
		<link>http://blog.danilocampos.com/2009/12/20/the-rediscovery-of-joy/</link>
		<comments>http://blog.danilocampos.com/2009/12/20/the-rediscovery-of-joy/#comments</comments>
		<pubDate>Mon, 21 Dec 2009 04:35:24 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Adventure]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Musings]]></category>
		<category><![CDATA[self-indulgent]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=420</guid>
		<description><![CDATA[When I was a kid I had two passions: Lego and the Macintosh. Lego was an instant bullet train to any world I could imagine. Space ships, robots, lunar colonies, pirate treasures, ancient castles, you name it. These were mine to explore. I could spend days at a time perfecting some imaginary construct made real [...]]]></description>
			<content:encoded><![CDATA[<p>When I was a kid I had two passions: Lego and the Macintosh.</p>
<p>Lego was an instant bullet train to any world I could imagine. Space ships, robots, lunar colonies, pirate treasures, ancient castles, you name it. These were mine to explore. I could spend days at a time perfecting some imaginary construct made real through the magic of Lego bricks and the exertion of my crude abilities. I treasured the rarer pieces, protecting closely my little snap-on magnet linkages and battery-driven light bricks. Regardless of whatever turbulent nonsense might have been happening elsewhere in my little world, Legos were an inviolable source of joy.</p>
<p>Joy, you know, that feeling that lives somewhere between the pit of your stomach and the tip of your smile. That vague something that builds a simple, contented glow inside of you that&#8217;s like a thousand perfect, extra-gravy-save-me-some-pie thanksgiving dinners with none of the bloated aftermath. Maybe you just saw the most beautiful vista in all of the world. Maybe you just fell in love. Maybe you&#8217;re ten years old and you see exactly what you were hoping for under the Christmas tree. You know what I&#8217;m saying, right?</p>
<p><em>Joy</em>.</p>
<p>The Macintosh came a bit later. At age 7, I got my hands on a borrowed <a href="http://en.wikipedia.org/wiki/Macintosh_SE">Macintosh SE</a>. And the joy was there, too. It could do <em>so many things</em>. It could produce clean, perfect type that was huge! I made a lot of paper signs. It could store all of this information and then show it to me again later. It could show me pictures and organize them into this tidy scrapbook.</p>
<p>And sounds! It made all these noises. I was most enamored with the quacking duck.</p>
<p>It was this whole world inside there that I could barely understand. I knew only one thing for certain: I wanted more. So much more of it.</p>
<p>Eventually, through about three years of begging and cajoling, I convinced my mom to plunk down the tidy sum necessary to secure a <a href="http://en.wikipedia.org/wiki/Power_Macintosh_6100">Performa 6116CD</a> for our exclusive home use. (The fact that I kept spending a lot of time at the home of a neighbor kid who had his own Mac and whom my mother intensely disliked probably sped things along, too.)</p>
<p>The 6116 was an even greater magnitude of joy. An 8x CD-ROM drive and a huge bundled library of multimedia content like encyclopedias and interactive atlases. Plus creativity applications and, wonder of wonders, Sim City 2000. I had so much fun exploring this new world. I spent an inordinate amount of time learning every piece of software I could get my hands on.</p>
<p>It was joy.</p>
<p>And then, I grew up. Like so many, I lost my capacity for the discovery of simple joy. It became the exception rather than the rule of life. Go to school, then go to work, do your job, go home, repeat.</p>
<p>Then I found programming. It occurred to me tonight, as I struggled, quite happily, to grasp how the hell it is block arguments in <a href="http://en.wikipedia.org/wiki/Ruby_(programming_language)">Ruby</a> work, that I&#8217;d rediscovered a simple, consistent source of joy. Programming languages are infinite bins of Lego blocks, waiting to be assembled to my liking. Programming is a limitlessly fascinating Performa, waiting for me to learn and harness any language for any task I can imagine. There&#8217;s just so much to learn and enjoy in programming computers.</p>
<p>Even after a few years of it, programming makes me feel joyfully like a kid again.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/12/20/the-rediscovery-of-joy/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>An Objective-C wrapper for AudioServicesPlaySystemSound</title>
		<link>http://blog.danilocampos.com/2009/12/14/an-objective-c-wrapper-for-audioservicesplaysystemsound/</link>
		<comments>http://blog.danilocampos.com/2009/12/14/an-objective-c-wrapper-for-audioservicesplaysystemsound/#comments</comments>
		<pubDate>Mon, 14 Dec 2009 07:36:41 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[objective-c]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=409</guid>
		<description><![CDATA[In Tallymander 2.0 I use a handful of sound effects to provide user feedback. I thought that the amount of code I had to write every time I wanted to play back a sound was a bit cumbersome so I wrote this wrapper around the AudioToolbox C functions I was using. DCSoundServices.h: #import &#60;Foundation/Foundation.h&#62; #import [...]]]></description>
			<content:encoded><![CDATA[<p>In Tallymander 2.0 I use a handful of sound effects to provide user feedback. I thought that the amount of code I had to write every time I wanted to play back a sound was a bit cumbersome so I wrote this wrapper around the AudioToolbox C functions I was using.</p>
<p>DCSoundServices.h:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #6e371a;">#import &lt;Foundation/Foundation.h&gt;</span>
<span style="color: #6e371a;">#import &lt;AudioToolbox/AudioToolbox.h&gt;</span>
&nbsp;
<span style="color: #a61390;">@interface</span> DCSoundServices <span style="color: #002200;">:</span> <span style="color: #400080;">NSObject</span> <span style="color: #002200;">&#123;</span>
&nbsp;
<span style="color: #002200;">&#125;</span>
&nbsp;
<span style="color: #002200;">+</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>playSoundWithName<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #400080;">NSString</span> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>fileName type<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #400080;">NSString</span> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>fileExtension;
<span style="color: #002200;">+</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>vibrateDevice;
&nbsp;
<span style="color: #a61390;">@end</span></pre></div></div>

<p>DCSoundServices.m:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #6e371a;">#import &quot;DCSoundServices.h&quot;</span>
&nbsp;
<span style="color: #a61390;">@implementation</span> DCSoundServices
&nbsp;
<span style="color: #002200;">+</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>playSoundWithName<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #400080;">NSString</span> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>fileName type<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #400080;">NSString</span> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>fileExtension
<span style="color: #002200;">&#123;</span>
&nbsp;
	CFStringRef cfFileName <span style="color: #002200;">=</span> <span style="color: #002200;">&#40;</span>CFStringRef<span style="color: #002200;">&#41;</span> fileName;
	CFStringRef cfFileExtension <span style="color: #002200;">=</span> <span style="color: #002200;">&#40;</span>CFStringRef<span style="color: #002200;">&#41;</span> fileExtension;
&nbsp;
	CFBundleRef mainBundle;
	mainBundle <span style="color: #002200;">=</span> CFBundleGetMainBundle <span style="color: #002200;">&#40;</span><span style="color: #002200;">&#41;</span>;
&nbsp;
	CFURLRef soundURLRef  <span style="color: #002200;">=</span> CFBundleCopyResourceURL <span style="color: #002200;">&#40;</span>mainBundle, cfFileName, cfFileExtension, <span style="color: #a61390;">NULL</span><span style="color: #002200;">&#41;</span>;
&nbsp;
	SystemSoundID soundID;
&nbsp;
	AudioServicesCreateSystemSoundID <span style="color: #002200;">&#40;</span>soundURLRef, <span style="color: #002200;">&amp;</span>soundID<span style="color: #002200;">&#41;</span>;
&nbsp;
&nbsp;
	AudioServicesPlaySystemSound <span style="color: #002200;">&#40;</span>soundID<span style="color: #002200;">&#41;</span>;
&nbsp;
	CFRelease<span style="color: #002200;">&#40;</span>soundURLRef<span style="color: #002200;">&#41;</span>;
&nbsp;
<span style="color: #002200;">&#125;</span>
&nbsp;
<span style="color: #002200;">+</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>vibrateDevice
<span style="color: #002200;">&#123;</span>
	AudioServicesPlaySystemSound<span style="color: #002200;">&#40;</span>kSystemSoundID_Vibrate<span style="color: #002200;">&#41;</span>;
<span style="color: #002200;">&#125;</span>
&nbsp;
<span style="color: #a61390;">@end</span></pre></div></div>

<p>Assuming you&#8217;ve got a sound file named click.aif in your application bundle, you can play a sound in just one line of code:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #002200;">&#91;</span>DCSoundServices playSoundWithName<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;click&quot;</span> type<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;aif&quot;</span><span style="color: #002200;">&#93;</span></pre></div></div>

<p>Or, vibrate the device:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #002200;">&#91;</span>DCSoundServices vibrateDevice<span style="color: #002200;">&#93;</span></pre></div></div>

<p>Much tidier than having to import AudioToolbox into every UI that ever plays back a sound. </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/12/14/an-objective-c-wrapper-for-audioservicesplaysystemsound/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Convert a CSV into a Plist file</title>
		<link>http://blog.danilocampos.com/2009/12/04/convert-a-csv-into-a-plist-file/</link>
		<comments>http://blog.danilocampos.com/2009/12/04/convert-a-csv-into-a-plist-file/#comments</comments>
		<pubDate>Fri, 04 Dec 2009 23:10:11 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[objective-c]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=404</guid>
		<description><![CDATA[Here&#8217;s some code from awhile back. It&#8217;ll take a CSV and turn it into a Plist file. The structure works like this: The first row of your CSV is considered to be the header. The code will use content from the header row to create key names. For every subsequent row, the code will create [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s some code from awhile back. It&#8217;ll take a CSV and turn it into a Plist file.</p>
<p>The structure works like this:</p>
<p>The first row of your CSV is considered to be the header. The code will use content from the header row to create key names. For every subsequent row, the code will create a dictionary, associating the content of each column with the key names from the header. The dictionaries live in an array which is written out to a Plist file.</p>
<p>This is handy if you&#8217;ve got a bunch of spreadsheet-type data you&#8217;d like to use as the basis for a static reference in your app. Say, data about the periodic table, airport listings, trivia questions, whatever. You can sift and sort your data in a user-friendly environment like Numbers or Excel, then dump it out to CSV and put this code to work.</p>
<p>It uses <a href="http://freshmeat.net/projects/ccsvparse">cCSVParse</a> to read the CSV file.</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #11740a; font-style: italic;">//	NSLog(@&quot;Converting %@&quot;,pathAsString);</span>
&nbsp;
	CSVParser <span style="color: #002200;">*</span>parser <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CSVParser new<span style="color: #002200;">&#93;</span>;
	<span style="color: #002200;">&#91;</span>parser openFileWithPath<span style="color: #002200;">:</span>pathAsString<span style="color: #002200;">&#93;</span>;
	<span style="color: #400080;">NSMutableArray</span> <span style="color: #002200;">*</span>csvContent <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>parser parseFile<span style="color: #002200;">&#93;</span>;
	<span style="color: #002200;">&#91;</span>parser closeFile<span style="color: #002200;">&#93;</span>;
&nbsp;
	<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>pathAsString <span style="color: #002200;">!=</span> <span style="color: #a61390;">nil</span><span style="color: #002200;">&#41;</span>
	<span style="color: #002200;">&#123;</span>
&nbsp;
		<span style="color: #400080;">NSArray</span> <span style="color: #002200;">*</span>keyArray <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>csvContent objectAtIndex<span style="color: #002200;">:</span><span style="color: #2400d9;">0</span><span style="color: #002200;">&#93;</span>;
&nbsp;
		<span style="color: #400080;">NSMutableArray</span> <span style="color: #002200;">*</span>plistOutputArray <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSMutableArray</span> array<span style="color: #002200;">&#93;</span>;
&nbsp;
		NSInteger i <span style="color: #002200;">=</span> <span style="color: #2400d9;">0</span>;
&nbsp;
		<span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span><span style="color: #400080;">NSArray</span> <span style="color: #002200;">*</span>array <span style="color: #a61390;">in</span> csvContent<span style="color: #002200;">&#41;</span>
		<span style="color: #002200;">&#123;</span>
&nbsp;
			<span style="color: #400080;">NSMutableDictionary</span> <span style="color: #002200;">*</span>dictionary <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSMutableDictionary</span> dictionary<span style="color: #002200;">&#93;</span>;
&nbsp;
			NSInteger keyNumber <span style="color: #002200;">=</span> <span style="color: #2400d9;">0</span>;
&nbsp;
			<span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span><span style="color: #400080;">NSString</span> <span style="color: #002200;">*</span><span style="color: #a61390;">string</span> <span style="color: #a61390;">in</span> array<span style="color: #002200;">&#41;</span>
			<span style="color: #002200;">&#123;</span>
&nbsp;
				<span style="color: #002200;">&#91;</span>dictionary setObject<span style="color: #002200;">:</span><span style="color: #a61390;">string</span> forKey<span style="color: #002200;">:</span><span style="color: #002200;">&#91;</span>keyArray objectAtIndex<span style="color: #002200;">:</span>keyNumber<span style="color: #002200;">&#93;</span><span style="color: #002200;">&#93;</span>;
&nbsp;
				keyNumber<span style="color: #002200;">++</span>;
&nbsp;
			<span style="color: #002200;">&#125;</span>
&nbsp;
			<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>i &gt; <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span>
			<span style="color: #002200;">&#123;</span>
				<span style="color: #002200;">&#91;</span>plistOutputArray addObject<span style="color: #002200;">:</span>dictionary<span style="color: #002200;">&#93;</span>;
			<span style="color: #002200;">&#125;</span>
&nbsp;
			i<span style="color: #002200;">++</span>;
&nbsp;
		<span style="color: #002200;">&#125;</span>
&nbsp;
<span style="color: #11740a; font-style: italic;">//		NSLog(@&quot;Plist output array %@&quot;, plistOutputArray);</span>
&nbsp;
		<span style="color: #400080;">NSMutableString</span> <span style="color: #002200;">*</span>mutableString <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSMutableString</span> stringWithString<span style="color: #002200;">:</span>pathAsString<span style="color: #002200;">&#93;</span>;
		<span style="color: #002200;">&#91;</span>mutableString replaceOccurrencesOfString<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;.csv&quot;</span> withString<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;.plist&quot;</span> options<span style="color: #002200;">:</span><span style="color: #a61390;">nil</span> range<span style="color: #002200;">:</span>NSMakeRange<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#91;</span>mutableString length<span style="color: #002200;">&#93;</span><span style="color: #002200;">-</span><span style="color: #2400d9;">4</span>, <span style="color: #2400d9;">4</span><span style="color: #002200;">&#41;</span><span style="color: #002200;">&#93;</span>;
&nbsp;
		<span style="color: #400080;">NSURL</span> <span style="color: #002200;">*</span>url <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSURL</span> fileURLWithPath<span style="color: #002200;">:</span>mutableString<span style="color: #002200;">&#93;</span>;
&nbsp;
<span style="color: #11740a; font-style: italic;">//		NSLog(@&quot;Write to URL %@&quot;,url);</span>
&nbsp;
		<span style="color: #002200;">&#91;</span>plistOutputArray writeToURL<span style="color: #002200;">:</span>url atomically<span style="color: #002200;">:</span><span style="color: #a61390;">YES</span><span style="color: #002200;">&#93;</span>;
&nbsp;
	<span style="color: #002200;">&#125;</span></pre></div></div>

<p>That&#8217;s from a simple desktop app I built for the purpose. It takes the path of your CSV, via drag-and-drop, then spits out the Plist beside it.  You can grab the <a href="http://code.google.com/p/danilobits/source/checkout">source</a> at Google Code. You can also download this <a href="http://danilobits.googlecode.com/files/Plist%20Converter1.0.zip">compiled binary</a> if you want to get crackin&#8217; right away.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/12/04/convert-a-csv-into-a-plist-file/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>How to Run a Great iPhone Beta Test</title>
		<link>http://blog.danilocampos.com/2009/12/03/how-to-run-a-great-iphone-beta-test/</link>
		<comments>http://blog.danilocampos.com/2009/12/03/how-to-run-a-great-iphone-beta-test/#comments</comments>
		<pubDate>Fri, 04 Dec 2009 02:05:25 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Stuff I Like]]></category>
		<category><![CDATA[beta testing]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=394</guid>
		<description><![CDATA[The App Store has over 100,000 apps available for purchase. That&#8217;s a big number. I can&#8217;t tell you the secret to success in the App Store. I haven&#8217;t discovered any magic formulas. The one thing I can tell you with 100% certainty is this: if you ship a product with bugs that genuinely piss off [...]]]></description>
			<content:encoded><![CDATA[<p>The App Store has over 100,000 apps available for purchase.</p>
<p>That&#8217;s a big number.</p>
<p>I can&#8217;t tell you the secret to success in the App Store. I haven&#8217;t discovered any magic formulas. The one thing I can tell you with 100% certainty is this: if you ship a product with bugs that genuinely piss off your customers or, worse, make them unable to use some portion of your hard work, the road toward success just got much longer and a lot more miserable. Even with &#8220;<a href="http://furbo.org/2009/07/23/waving-a-red-flag/">prioritized review</a>&#8221; requests, once your buggy app is on the store, the review queue means you could be waiting a long time before your customers can enjoy your bug fix.</p>
<p>You will not sleep well at night.</p>
<p>The good news is that this problem has been solved a thousand times over. A thorough beta test can save you from this terrible outcome and the sleepless nights that come with it. With a bit of diligence, you can ship an app with awesome stability to match your great ideas.</p>
<p>I&#8217;ll share how I run my betas. This is about process &#8212; the technical side of Ad Hoc distribution is thoroughly documented by Apple and <a href="http://johnehartzog.com/2009/04/iphone-app-ad-hoc-gotchas/">others</a>. The tools I use are simple and free. They work. If you have suggestions for better ones, I want to know about them before my next test! Please share in the comments.</p>
<h2>Gather Your Testers</h2>
<p>You&#8217;re going to need testers. If you use a volunteer testing pool, figure that 80% of testing will be done by 20% of your volunteers. That means to have any hope of gathering meaningful feedback, you&#8217;ll need at least 10 volunteers. 15 or 20 would be the ideal minimum.</p>
<p>Next, you&#8217;ll need to start early &#8212; but not so early that your testers&#8217; enthusiasm cools by the time you ship your first beta build. Try to get the word out between three to six weeks before you&#8217;re ready to test. If you have a particularly strong network, you may need less time. If you&#8217;re relying more on the kindness of strangers, though, make sure to give yourself plenty of lead time. Use your website, your blog and your Twitter to solicit volunteers but don&#8217;t be a pest.</p>
<p>Most importantly: if you have contact with existing customers from previous versions or perhaps other products entirely, send a quick, polite email explaining what you&#8217;re going to test and encouraging them to join up. Customers who have emailed you in the past with feedback or feature requests may be especially keen on seeing your latest work.</p>
<h2>Understand Your Testing Pool</h2>
<p>You need to know things about your testers. What kind of hardware will they be using? What version of iPhone OS? What&#8217;s their geographic region? (International testers are crucial for finding your screwups with localized formatting and other geographic issues.) Most importantly, you&#8217;re going to need to collect their UUID.</p>
<p>I use <a href="http://docs.google.com/support/bin/answer.py?hl=en&amp;answer=87809">Forms</a> for Google Docs to collect most of my feedback. It&#8217;s an easy-to-configure way to store structured data. Since you can designate required fields, you can ensure your testers don&#8217;t forget to include anything crucial when reporting issues. Best of all, you can embed the forms into existing web pages, allowing you to include them alongside testing guidance, version history or other information that might be useful.</p>
<p>Here&#8217;s the form I use to collect beta tester info:</p>
<p><iframe src="http://spreadsheets.google.com/embeddedform?key=0As-F9sF1VnYddFltZHZVX3llNVFIR29KSms0ZF9uR0E" width="700" height="400" frameborder="0" marginheight="0" marginwidth="0">Loading&#8230;</iframe></p>
<p>It&#8217;s not short. The other benefit to using a form is to ensure you only get testers who are reasonably skilled at providing detailed information. Everyone who makes it past this filter has a decent shot at actually telling you everything you need to know later in the beta.</p>
<h2>Betas Worth Testing</h2>
<p>If your app doesn&#8217;t look somewhat close to the final product, don&#8217;t distribute it for testing. If you&#8217;re relying on a volunteer testing pool, you want them eager to use your stuff. If your overall UI is incomplete, you&#8217;re not ready to test. You&#8217;ve got a limited amount of time and attention with your testers. Don&#8217;t squander it on early builds you could easily test yourself. The closer you think your app is to shipping, the more useful your testers will be to you.</p>
<p>You do yourself no favors if the last bit of functionality you add affects previously-solid features that now need a whole new round of testing.</p>
<p><a href="http://www.danilocampos.com/2009/06/globejot-10-removed-from-sale-pending-rewrite/">Believe me.</a></p>
<h2>Distributing Your Binaries</h2>
<p>So you&#8217;ve toiled away and you&#8217;ve finally got an app you feel is ready to be tested.</p>
<p><em>Don&#8217;t email the binary</em>.</p>
<p>Depending on the size of your app, the attachment may not be properly delivered to the recipient, especially if they&#8217;re using Exchange-hosted email. Instead, host your app and the necessary provisioning profile on the web.</p>
<p>I also recommend a simple checkout system. When you&#8217;ve got a new build ready for testing, send your testers a link to a one-question form that asks for their email address. On the form confirmation screen, you can provide a link to the binary. This lets you get an idea of who is participating. It also lets you answer an important question: am I getting zero feedback because there are no bugs or because there are no active testers? With a checkout process, you&#8217;ll know for sure.</p>
<p>Use clear versioning with every distributed build. You need to be certain you and your users are talking about the same build when they give you feedback. Beta phases, version numbers, dates, whatever you need to make things clear for everyone involved. Make sure that somewhere in your app, your code pulls the version number out of your app&#8217;s Info.plist file and displays it in the UI. Here&#8217;s some code to get that string:</p>
<pre>
NSString *currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];</pre>
<p>Update your Info.plist&#8217;s version number religiously before distribution. Even better, <a href="http://iphonedevelopment.blogspot.com/2009/04/automated-commit-and-build-number.html">update it automatically</a>.</p>
<h2>Communication</h2>
<p>You need to communicate with your testing pool. Keeping them regularly updated, without being spammy, is crucial to keeping them excited about what&#8217;s going on.</p>
<p>When you send out a new build, explain what&#8217;s new. Give specific testing guidance so that your testers know where they should focus their efforts. New method of rendering cells you strongly suspect has a bug or two? Share your hunches. Built a new import subsystem you&#8217;re suspicious might break with legacy data? Provide an example data set or encourage existing users of the old version to give it a whirl. If you&#8217;re manipulating data that lives only on the user&#8217;s device, tell them to <em>back up</em> their iPhone before running your beta. And tell them why. Don&#8217;t be a douche on this: never take for granted the importance of your testers&#8217; application data, especially if you&#8217;re lucky enough to have your existing customers helping you. Yes, we all know, betas are risky. Still, the iPhone isn&#8217;t the desktop and if you&#8217;re testing an upgrade to an existing app, your customers may have data they created before accepting the risks of a beta.</p>
<p>If it has been more than seven to ten days since your last build, send a quick update to the pool. Call out awesome testers and let them know they&#8217;re making a difference. Share the score of resolved issues you were able to fix thanks to the pool&#8217;s feedback. Most of all, thank them. Their efforts are helping you to not look like an idiot.</p>
<h2>Sandboxing Helps</h2>
<p>If you&#8217;re making an all-new app, you can skip this section. If you&#8217;re testing an upgrade to an existing app, read on.</p>
<p>Your early beta builds should use a different app identifier than your shipping app. This way, your beta will be installed as a distinct application alongside your currently shipping version on the user&#8217;s device, instead of overwriting it. This lets you ensure that no shenanigans happen to legacy data if you&#8217;ve got customers from previous versions in your testing pool. Toward the end of your beta, after much internal testing, you can start distributing builds that overwrite previous shipping versions. You&#8217;ll want to ensure that the app properly imports legacy data and preferences and, of course, ensure nothing otherwise disastrous happens when iTunes distributes your updated app to existing customers with existing data.</p>
<h2>Gather Useful Feedback</h2>
<p>You need to know things about how your application is failing. The forms I use are inspired heavily by what I saw once upon a time during the iPhone OS 2.0 Enterprise AppleSeed beta test. I think it covers the basics. Of course, don&#8217;t be afraid to add other questions specific to your application&#8217;s circumstances. For bugs, measure how well you can reproduce the bug and its overall severity while capturing the reproduction steps. For feature or enhancement requests, measure the tester&#8217;s impression of the request&#8217;s importance along with what they&#8217;re asking for.</p>
<p><iframe src="http://spreadsheets.google.com/embeddedform?key=0As-F9sF1VnYdcmdVVGJsSklNUU53cFo4RHphWFZCcUE" width="700" height="400" frameborder="0" marginheight="0" marginwidth="0">Loading&#8230;</iframe></p>
<p><iframe src="http://spreadsheets.google.com/embeddedform?key=0As-F9sF1VnYdcjl1MHN5Z0RyNDBSTE1TUkt3TW51UFE" width="700" height="400" frameborder="0" marginheight="0" marginwidth="0">Loading&#8230;</iframe></p>
<p>However you gather your feedback, ensure that every single message you send to your testing pool includes brief instructions on how your testers should report their bugs. Never make them hunt around for it.</p>
<h2>Ship</h2>
<p>You haven&#8217;t added or changed anything for a couple of builds. You&#8217;ve fixed plenty of bugs and no one has reported regressions. You&#8217;ve sent emails to your top-performing testers and they&#8217;re giving you a thumbs-up.</p>
<p>Give it one last test yourself. Thirty minutes, minimum, actually doing things your users are likely to do. Is it kosher?</p>
<p>Then ship it.</p>
<p>Then, while you wait for approval from Apple, test it some more, informally, yourself. If somehow a show-stopping bug pops up while you await approval, you can reject your binary and submit a fix before it ruins anyone&#8217;s day.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/12/03/how-to-run-a-great-iphone-beta-test/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>iPhone Development: Force UITableView to show only complete cells</title>
		<link>http://blog.danilocampos.com/2009/10/29/iphone-development-force-uitableview-to-show-only-complete-cells/</link>
		<comments>http://blog.danilocampos.com/2009/10/29/iphone-development-force-uitableview-to-show-only-complete-cells/#comments</comments>
		<pubDate>Fri, 30 Oct 2009 00:49:17 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Neurotic]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[cocoa touch]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[objective-c]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=341</guid>
		<description><![CDATA[Tallymander uses UITableViewCells not only to display data but also to manipulate it. That means that partially-visible cells &#8212; that is, those that are cut off at the top or bottom of the view &#8212; aren&#8217;t terribly useful. Better, I thought, would be to always ensure that when a partially-visible cell crops up, the UITableViewController [...]]]></description>
			<content:encoded><![CDATA[<p>Tallymander uses UITableViewCells not only to display data but also to manipulate it. That means that partially-visible cells &#8212; that is, those that are cut off at the top or bottom of the view &#8212; aren&#8217;t terribly useful.</p>
<p>Better, I thought, would be to always ensure that when a partially-visible cell crops up, the UITableViewController will quietly nudge things to be completely visible.</p>
<p><a href="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo6.jpg" rel="shadowbox[sbpost-341];player=img;" title="Partially visible cells" rel="lightbox[341]"><img class="alignnone size-medium wp-image-354" title="Partially visible cells" src="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo6-200x300.jpg" alt="Partially visible cells" width="200" height="300" /></a> vs. <a href="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo-2.jpg" rel="shadowbox[sbpost-341];player=img;" title="Fully Visible Cells" rel="lightbox[341]"><img class="alignnone size-medium wp-image-355" title="Fully Visible Cells" src="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo-2-200x300.jpg" alt="Fully Visible Cells" width="200" height="300" /></a></p>
<p>In the first image, you can see the only part of the top and bottom cell. In the second image, the cells have been nudged so that everything visible is <em>completely</em> visible. This took a little noodling around with math but it wasn&#8217;t hard to do. Put this method into your UITableViewController subclass:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>snapBottomCell
<span style="color: #002200;">&#123;</span>
	NSInteger cellHeight <span style="color: #002200;">=</span> <span style="color: #2400d9;">62</span>; <span style="color: #11740a; font-style: italic;">//Cells for my view are 62px tall. Sub your own height here</span>
&nbsp;
	NSInteger offsetOverage <span style="color: #002200;">=</span> <span style="color: #002200;">&#40;</span>NSInteger<span style="color: #002200;">&#41;</span> self.tableView.contentOffset.y <span style="color: #002200;">%</span> cellHeight;
	<span style="color: #11740a; font-style: italic;">//Use the tableview's contentOffset property and the cell height to determine how much is being cut off</span>
&nbsp;
	<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>offsetOverage <span style="color: #002200;">&amp;</span>gt; <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span>
	<span style="color: #002200;">&#123;</span>
		<span style="color: #11740a; font-style: italic;">//If the overage is more than 0, we should figure out what the new offset needs to be</span>
&nbsp;
		NSInteger newOffset;
&nbsp;
		<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>offsetOverage <span style="color: #002200;">&amp;</span>gt;<span style="color: #002200;">=</span> <span style="color: #002200;">&#40;</span>cellHeight<span style="color: #002200;">/</span><span style="color: #2400d9;">2</span><span style="color: #002200;">&#41;</span><span style="color: #002200;">&#41;</span>
		<span style="color: #002200;">&#123;</span>
			newOffset <span style="color: #002200;">=</span> self.tableView.contentOffset.y <span style="color: #002200;">+</span> <span style="color: #002200;">&#40;</span>cellHeight <span style="color: #002200;">-</span> offsetOverage<span style="color: #002200;">&#41;</span>;
			<span style="color: #11740a; font-style: italic;">//If the overage is greater than or equal to half the height of a cell, pull the cell up so it's fully visible</span>
		<span style="color: #002200;">&#125;</span>
&nbsp;
		<span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span>
			newOffset <span style="color: #002200;">=</span> self.tableView.contentOffset.y <span style="color: #002200;">-</span> offsetOverage;
			<span style="color: #11740a; font-style: italic;">//Else, push the cell out of view</span>
		<span style="color: #002200;">&#125;</span>
&nbsp;
		<span style="color: #11740a; font-style: italic;">//With the new offset determined, animate the movement:</span>
&nbsp;
		<span style="color: #002200;">&#91;</span>UIView beginAnimations<span style="color: #002200;">:</span><span style="color: #a61390;">nil</span> context<span style="color: #002200;">:</span><span style="color: #a61390;">NULL</span><span style="color: #002200;">&#93;</span>;
		<span style="color: #002200;">&#91;</span>UIView setAnimationDuration<span style="color: #002200;">:</span><span style="color: #2400d9;">0.5</span><span style="color: #002200;">&#93;</span>;
		<span style="color: #002200;">&#91;</span>UIView setAnimationCurve<span style="color: #002200;">:</span>UIViewAnimationCurveEaseOut<span style="color: #002200;">&#93;</span>;
		<span style="color: #002200;">&#91;</span>self.tableView setContentOffset<span style="color: #002200;">:</span>CGPointMake<span style="color: #002200;">&#40;</span><span style="color: #2400d9;">0</span>, newOffset<span style="color: #002200;">&#41;</span> animated<span style="color: #002200;">:</span><span style="color: #a61390;">NO</span><span style="color: #002200;">&#93;</span>;
		<span style="color: #002200;">&#91;</span>UIView commitAnimations<span style="color: #002200;">&#93;</span>;
&nbsp;
	<span style="color: #002200;">&#125;</span>
<span style="color: #002200;">&#125;</span></pre></div></div>

<p>You&#8217;ll need to implement these two delegate methods as well:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;"><span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>scrollViewDidEndDecelerating<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>UIScrollView <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>scrollView
<span style="color: #002200;">&#123;</span>
	<span style="color: #002200;">&#91;</span>self snapBottomCell<span style="color: #002200;">&#93;</span>;
<span style="color: #002200;">&#125;</span>
&nbsp;
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>scrollViewDidEndDragging<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>UIScrollView <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>scrollView willDecelerate<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">BOOL</span><span style="color: #002200;">&#41;</span>decelerate
<span style="color: #002200;">&#123;</span>
	<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>decelerate <span style="color: #002200;">==</span> <span style="color: #a61390;">NO</span><span style="color: #002200;">&#41;</span>
	<span style="color: #002200;">&#123;</span>
		<span style="color: #002200;">&#91;</span>self snapBottomCell<span style="color: #002200;">&#93;</span>;
	<span style="color: #002200;">&#125;</span>
<span style="color: #002200;">&#125;</span></pre></div></div>

<p>And that&#8217;s it. If more than half of the bottom cell is visible, the cell gets nudged completely into view. If less than half is visible, it&#8217;s pushed out of view.</p>
<p>You could change snapBottomCell to accept cellHeight as an argument if you need to accommodate cells with variable heights. You might sort out a particular cell&#8217;s height like so:</p>

<div class="wp_syntax"><div class="code"><pre class="objc" style="font-family:monospace;">	<span style="color: #400080;">NSIndexPath</span> <span style="color: #002200;">*</span>indexPath <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>self.tableView indexPathForRowAtPoint<span style="color: #002200;">:</span>CGPointMake<span style="color: #002200;">&#40;</span><span style="color: #2400d9;">0</span>, <span style="color: #002200;">&#40;</span>self.tableView.frame.size.height <span style="color: #002200;">-</span> <span style="color: #2400d9;">1</span><span style="color: #002200;">&#41;</span><span style="color: #002200;">&#41;</span><span style="color: #002200;">&#93;</span>;
	<span style="color: #11740a; font-style: italic;">//Get the index path of the cell currently visible at the bottom edge of the tableview</span>
&nbsp;
	UITableViewCell <span style="color: #002200;">*</span>cell <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>self.tableView cellForRowAtIndexPath<span style="color: #002200;">:</span>indexPath<span style="color: #002200;">&#93;</span>;
&nbsp;
	<span style="color: #002200;">&#91;</span>self snapBottomCell<span style="color: #002200;">:</span>cell.frame.size.height<span style="color: #002200;">&#93;</span>;</pre></div></div>

<p>There are cases where this isn&#8217;t useful: long lists whose primary function is selecting data for loading in another view, for example. Still, any time you&#8217;re using UITableViewCells to house controls or chunky bits of data, it might be good to ensure that only whole cells are displayed.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/10/29/iphone-development-force-uitableview-to-show-only-complete-cells/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Tallymander 2.0 Post-Mortem</title>
		<link>http://blog.danilocampos.com/2009/10/20/tallymander-2-0-post-mortem/</link>
		<comments>http://blog.danilocampos.com/2009/10/20/tallymander-2-0-post-mortem/#comments</comments>
		<pubDate>Tue, 20 Oct 2009 21:14:50 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[User Experience]]></category>
		<category><![CDATA[tallymander]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=332</guid>
		<description><![CDATA[Tallymander 2.0 has been in the App Store for a few weeks. Here&#8217;s the story on my favorite product, so far. Once upon a time&#8230; Tallymander was born because once upon a time, I worked for the man and that meant wearing fancy pants and shirts that buttoned (the better to establish my image as [...]]]></description>
			<content:encoded><![CDATA[<p>Tallymander 2.0 has been in the App Store for a few weeks. Here&#8217;s the story on my favorite product, so far.</p>
<h2 style="font-size: 1.5em;">Once upon a time&#8230;</h2>
<p>Tallymander was born because once upon a time, I worked for the man and that meant wearing fancy pants and shirts that buttoned (the better to establish my image as a professional and advance my career). About once a month I rounded up my finery, counted it, and took it to the dry cleaners.</p>
<p>My mental math abilities could be bested by a fruit fly so I wondered if the App Store had anything to make the counting easier. They did, but it was either crap or too limited for my needs. The idea for Tallymander was born. I wanted to make something simple but useful.</p>
<p>I expected to make about $40 off of it, but I knew I wanted the challenge so I plowed ahead anyway. 1.0 shipped and people liked it. Really liked it! I got a handful of emails with kudos and feature requests. I wanted to get cracking on <a href="http://www.danilocampos.com/apps/globejot/">GlobeJot</a> but decided I would spend a little time adding some customer requests to Tallymander first. A couple more weeks of development and Tallymander 1.1 was submitted. I forgot all about it.</p>
<p>Until one night, around midnight, when I got the &#8220;Ready for Sale&#8221; email that iPhone developers live for. Tallymander 1.1 was live.</p>
<p>And an App Store Staff Favorite. I peed myself a little. There followed a slew of <a href="http://blog.danilocampos.com/2009/03/20/lots-of-tally-counters/">copycat apps</a> following this prominence. So it goes on the App Store.</p>
<h2 style="font-size: 1.5em;">The future</h2>
<p>Tthanks to a handful of enthusiastic users and a little help from Apple, Tallymander managed to make me a tidy bit more money than the $40 I had expected. About $4,000 to date, in fact. Not bad for an app that started as a two week UX trial.</p>
<p><span id="more-332"></span></p>
<p>It wasn&#8217;t enough for me, though. The best thing about Tallymander was getting emails from customers with wildly unexpected and diverse use cases. I haven&#8217;t gotten a single one that&#8217;s like another. The greatest commonality across all the customer emails boiled down to two main themes: better organization and data analysis.</p>
<p>My customers weren&#8217;t the only ones with ideas on how to make Tallymander better. A minor point release with a bug fix was rejected by Apple in April. The reason? The reviewer didn&#8217;t understand how an existing interface element should work and rejected because it didn&#8217;t work according to his expectations.</p>
<p>The rejection was dumb &#8212; the previous major release that had introduced the interface element in question had been approved just fine. But then it dawned on me: the interface sucks.</p>
<h2 style="font-size: 1.5em;">Kill the sacred cows</h2>
<p>Tallymander&#8217;s interface had won many fans, even at Apple, but there was one problem: it was too inflexible. To tally, you tapped anywhere on a tally&#8217;s cell:</p>
<p><a href="http://www.danilocampos.com/wp-content/uploads/2009/10/IMG_0080.jpg" rel="shadowbox[sbpost-332];player=img;" title="IMG_0080" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="IMG_0080" src="http://www.danilocampos.com/wp-content/uploads/2009/10/IMG_0080.jpg" alt="IMG_0080" width="320" height="480" /></a>Each cell had a little indicator that showed what mode it was in: addition or subtraction. You could temporarily change all modes globally with a sort of &#8220;shift key&#8221; in the toolbar. 1.2 introduced a &#8220;caps lock&#8221; style option for this shift mode, which is when the whole UI, for me, went to hell.</p>
<h2 style="font-size: 1.5em;">Mission 1: Fix the interface</h2>
<p>Tallymander&#8217;s interface, where it worked, worked really well. First, you could count an infinite number of things. Since it used UITableView, which can store as many cells as you want, it could scale to many tasks. Whether you&#8217;re counting two things or twenty, Tallymander had you covered. This was an enormous advantage over some of the other early tallying apps.</p>
<p>The other major benefit was ease of interaction. The hit box for the user to interact with any given tally was 320 x 80 pixels. Large targets are easier to hit. Big win, especially if your attention is on something other than your iPhone.</p>
<p>The secret sauce to Tallymander, the reason why people love it, is the one that has evaded almost every other competitor. There&#8217;s a user-definable tally setting that lets you shake the iPhone to increment the count. Tallymander  vibrates when a successful tally is registered this way. The first time someone does this, their eyes light up. Then they do it again. And again. And again. It <em>feels </em>good. So no matter what else, that had to stay.</p>
<p>Aside from these things, it was time to take the rest of the UI out and shoot it. Shift mode? That sucks. Separate modes to reorder and edit? That&#8217;s crap.</p>
<p>Ending shift mode meant add and subtract had to live together, in harmony, at all times where you&#8217;d be tallying. It took many sketches to get something I wanted, but I ended up with this configuration:</p>
<p><a href="http://www.danilocampos.com/wp-content/uploads/2009/10/oldbasecell.png" rel="shadowbox[sbpost-332];player=img;" title="oldbasecell" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="oldbasecell" src="http://www.danilocampos.com/wp-content/uploads/2009/10/oldbasecell.png" alt="oldbasecell" width="320" height="60" /></a>Tap anywhere on the left, you get subtraction. Tap anywhere on the right, you get addition. The hit box is still pretty generous and the tradeoff in the ease of switching between those two tasks &#8212; just move your finger &#8212; made it worth doing. The cell itself is also smaller so that more tallies can appear onscreen at once. The other major change: no more alarm clock style numerals. They had to be pre-rendered as images and while I liked the look, the pre-rendering limited the kinds of data that could be displayed in the counter field. The new counter is stylized in homage to the old, replacing its seven-segment grunge with clean OLED-style pixelation.</p>
<p>The death of shift mode is exaggerated. It&#8217;s still around, but now it makes more sense. It is <em>config</em> mode and it shifts all cells into a very different realm of tasks: resetting counts and changing tally-specific settings.</p>
<p><a href="http://www.danilocampos.com/wp-content/uploads/2009/10/oldconfigmode.png" rel="shadowbox[sbpost-332];player=img;" title="oldconfigmode" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="oldconfigmode" src="http://www.danilocampos.com/wp-content/uploads/2009/10/oldconfigmode.png" alt="oldconfigmode" width="320" height="60" /></a>This works very well. You can switch into config mode, make all the changes you like, then get back to your tallying. This is a lot closer to how the user works: adding and subtracting both fit in the same basic mental mode, in the end, and it&#8217;s counterproductive to separate them.</p>
<p>Best of all, deletion and reordering were reunited as one edit mode:</p>
<p><a style="text-decoration: none;" href="http://www.danilocampos.com/wp-content/uploads/2009/10/photo.jpg" rel="shadowbox[sbpost-332];player=img;" title="photo" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="photo" src="http://www.danilocampos.com/wp-content/uploads/2009/10/photo.jpg" alt="photo" width="320" height="253" /></a></p>
<p>With addition and subtraction living in one spot with no clearly defined button, it was important to give the user some kind of visual feedback. Most buttons on the iPhone darken as the user touches, then register an action when the touch lifts. This was inappropriate for Tallymander &#8212; counting needs instantaneous feedback. Delays lead to confusion. Instead of darkening a region, Tallymander 2.0 glows according to whatever interaction the user has selected:</p>
<p><a href="http://www.danilocampos.com/wp-content/uploads/2009/10/glow.png" rel="shadowbox[sbpost-332];player=img;" title="glow" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="glow" src="http://www.danilocampos.com/wp-content/uploads/2009/10/glow.png" alt="glow" width="320" height="60" /></a>The glow matches the color of whatever gem corresponds to the action the user selected. Here, the user has tapped the addition side of the cell. There&#8217;s also a satisfying click sound, for users who want it.</p>
<p>Finally, I hated the initial look of the UI and rebuilt it. The shipping app looks like this:</p>
<p style="text-align: center;">
<p style="text-align: center;"><a style="text-decoration: none;" href="http://www.danilocampos.com/wp-content/uploads/2009/10/Basketball.jpg" rel="shadowbox[sbpost-332];player=img;" title="Basketball" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="Basketball" src="http://www.danilocampos.com/wp-content/uploads/2009/10/Basketball.jpg" alt="Basketball" width="192" height="288" /></a></p>
<p style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="BasketBallEditing" src="http://www.danilocampos.com/wp-content/uploads/2009/10/BasketBallEditing.jpg" alt="BasketBallEditing" width="192" height="288" /></p>
<p>The last excision of suck was the reset confirmation. Previously, Tallymander popped up an action sheet asking the user to confirm whether or not they wanted to reset a given count. Time consuming. Worse, this option could be switched off on a per-tally basis, leading to inconsistent expectations. In 2.0, tapping a cell&#8217;s orange reset button puts it into a temporary &#8220;are you sure?&#8221; mode, changing its title label with instructions to confirm. If the user taps again, the count resets. If not, after a brief delay, it switches harmlessly back out into its normal mode.</p>
<p style="text-align: center;"><a href="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo-9.jpg" rel="shadowbox[sbpost-332];player=img;" title="photo-9" rel="lightbox[332]"><img class="aligncenter size-full wp-image-333" title="photo-9" src="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo-9.jpg" alt="photo-9" width="320" height="188" /></a></p>
<p>This lets experienced users quickly reset things while preventing accidental data loss. It&#8217;s a small detail but it&#8217;s one of my favorites.</p>
<p>Another small refinement: the table that contains the tallies will never come to a stop with a tally half-on, half-off the screen. The cells are always nudged so that they&#8217;re completely visible.</p>
<h2 style="font-size: 1.5em;">Mission 2: Customer requests</h2>
<p>You&#8217;ve got a lot of data. You&#8217;ve got questions about your data. What do you do?</p>
<p>If you&#8217;re using a spreadsheet, you type up an equation for some quick analysis. I&#8217;m a huge spreadsheet nerd, so I like this freeform approach. Customers wanted percentages of this and that and while I could easily provide a one-size-fits-all, pre-canned percentage tool, that&#8217;s not how I roll.</p>
<p>Tallymander 2 lets you build chains of equations, called computations. Comps can use any tally as input. They can also use other comps. You build comps with this UI:</p>
<p><a href="http://www.danilocampos.com/wp-content/uploads/2009/10/photo1.jpg" rel="shadowbox[sbpost-332];player=img;" title="photo" rel="lightbox[332]"><img style="display: block; margin-left: auto; margin-right: auto; border: 0px initial initial;" title="photo" src="http://www.danilocampos.com/wp-content/uploads/2009/10/photo1.jpg" alt="photo" width="320" height="480" /></a>Each step uses the result of the previous step as its first input, lets the user select an operation, then a second input. As each input changes, the result of the computation is updated accordingly. You can even format the final result as a percentage. This kind of data analysis has a lot of benefits, especially for those keeping scores for athletics or tabletop gaming.</p>
<p>I&#8217;m pretty happy with how this came out, with one snag: since there&#8217;s no way to do PEMDAS with this, you need to set up other computations to handle parenthetical parts of a given equation. 3 + (Red roses x <img src='http://blog.danilocampos.com/wp-includes/images/smilies/icon_cool.gif' alt='8)' class='wp-smiley' /> &#8211; 4, for example, would require the creation of two computations. I think I can solve this by allowing sub-computations that don&#8217;t show up in the many tally list. Something for 2.1</p>
<p>The other big request was the ability to group tallies together.</p>
<p>Done. Set up as many groups as you like. An implementation detail currently means you can&#8217;t move a tally from one group into another. I&#8217;ll find a way around it for 2.1.</p>
<h2>Mission 3: Object Model</h2>
<p>Implicit in any new version of Tallymander was the need to create a proper object model. The Tallymander 1.x model was, and I&#8217;m cringing here, <em>a collection of NSDictionary objects</em> stored to, yeah, <em>NSUserDefaults</em>. C&#8217;mon! This is not how you want to build your object graph, folks. It was an easy way to wrap my mind around the data I needed to store early on. It worked in the beginning but only with much nasty, nasty code.</p>
<p>Luckily, two things happened since I first wrote Tallymander. First, I learned how to build model objects and their relationships by hand. Second, Apple brought <a href="http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html">Core Data</a> to the iPhone, which does all that heavy lifting for you. For 2.0, I was ready to do it right.</p>
<p>Joyful. That&#8217;s the only word to describe working with Core Data. It&#8217;s an outstanding, powerful and well documented framework. What would have taken me days to code by hand I can set up in Core Data in one afternoon. I hit an occasional bump or two but always had Stack Overflow to get me back out of the woods. Very glad to have this tool as a developer. 2.0 would have taken a lot longer without it.</p>
<p>But I wasn&#8217;t done with my rickety NSDictionary-based model from 1.x. I got to play with it one last time when I wrote the import code that converted the old data to use the new, shiny model. The worst code is what you wrote eight months ago, I swear it.</p>
<h2>Kitchen sink</h2>
<p>Thanks to Core Data, it was easy to build a pile of new options for tallies. Some of these are customer requests, some are just ideas I had that might be fun.</p>
<p>One of my favorites is the icon picker:</p>
<p><a href="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo3.jpg" rel="shadowbox[sbpost-332];player=img;" title="photo" rel="lightbox[332]"><img class="aligncenter size-full wp-image-334" title="photo" src="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo3.jpg" alt="photo" width="320" height="480" /></a>Twirl the wheel to change the icon. There are about fifty to choose from. Of all the UIs I&#8217;ve ever built, this is one of the most fun to use. The icon in the preview at the top updates as the wheel is moved. I like playing with it.</p>
<p>The icons are from the excellent <a href="http://www.glyphish.com">Glyphish</a> collection. Awhile back I contributed to the author&#8217;s <a href="http://www.kickstarter.com/projects/jpwain/awesome-icons-for-your-iphone-apps">Kickstarter project</a>. As a backer, I was rewarded with access to the source vector files that make up the icons. Useful stuff and a great starting point to build Tallymander&#8217;s icon collection. Sadly, Tallymander is <a href="http://spreadsheets.google.com/pub?key=tcQpWLmvqLZ7Egc4m5G9e2w&amp;single=true&amp;gid=0&amp;output=html">not blessed</a> with the Glyphish &#8220;awesome&#8221; designation, but you can&#8217;t win &#8216;em all.</p>
<p>If the built-in icons don&#8217;t strike your fancy, you can insert one from your library, too.</p>
<p>I&#8217;m also liking the color picker:</p>
<p><a href="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo4.jpg" rel="shadowbox[sbpost-332];player=img;" title="Color Picker" rel="lightbox[332]"><img class="aligncenter size-full wp-image-335" title="Color Picker" src="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo4.jpg" alt="Color Picker" width="320" height="480" /></a></p>
<p>Another advantage of ditching pre-rendered graphics for the counters: color options galore. I picked some tasteful ones, mostly from Apple&#8217;s Crayon Picker palette.  I like this arrangement better than picking colors off of a list. Everything is visible on the screen at once.</p>
<p>Broke some new ground with custom UI in 2.0. In all, I like how it came out. These are big wins that add a lot to the user&#8217;s ability to make Tallymander their own.</p>
<h2 style="font-size: 1.5em;">Sharing</h2>
<p>The final chunk of functionality that needed to be built was better sharing options. 1.x allowed an email report to be sent from the application. This was decent but with all the new features and depth in 2.0, I wanted to go further.</p>
<p>CSV export ended up being much, much easier than I could have guessed. So that&#8217;s in there, it works and I&#8217;m happy.</p>
<p><a href="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo5.jpg" rel="shadowbox[sbpost-332];player=img;" title="Tallymander Sharing" rel="lightbox[332]"><img class="aligncenter size-full wp-image-336" title="Tallymander Sharing" src="http://blog.danilocampos.com/wp-content/uploads/2009/10/photo5.jpg" alt="Tallymander Sharing" width="320" height="480" /></a></p>
<p>More exciting is every time I play with the new, full-on sharing feature. Tallymander will let you send your whole setup to someone else.  It&#8217;s a combination of Core Data, Amazon S3 and the <a href="http://allseeing-i.com/ASIHTTPRequest/">ASIHTTPRequest</a> API. ASIHTTPRequest is a superb bit of code. Drop it in, read the docs and you&#8217;re putting and getting data from the web. Stable, easy to understand and an enthusiastic developer at the wheel. Saved me much pain, I&#8217;m certain.</p>
<h2 style="font-size: 1.5em;">And beyond</h2>
<p>So that&#8217;s 2.0. Already I have a mounting list of new refinements and additions I&#8217;d like to make. It&#8217;s a fun app, though, and my proudest yet. Thanks to my enthusiastic customers. I couldn&#8217;t have done 2.0 without your excellent feedback. It was quite a trip: Tallymander 1.2 had about 2,000 lines of code in it. 2.0 has around 8,000 and it&#8217;s a complete rewrite.</p>
<p>I&#8217;m excited to keep tearing into the future of Tallymander and my other apps. As the first full-featured app I&#8217;ve built since going full-time, Tallymander really helped me find my tone and style as an iPhone developer. This stuff is so much fun.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/10/20/tallymander-2-0-post-mortem/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

