<?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; iPhone</title>
	<atom:link href="http://blog.danilocampos.com/category/iphone/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.danilocampos.com</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Tue, 17 Aug 2010 04:28:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>iPhone Development for Beginners &#8211; 2010 Edition</title>
		<link>http://blog.danilocampos.com/2010/01/20/iphone-development-for-beginners-2010-edition/</link>
		<comments>http://blog.danilocampos.com/2010/01/20/iphone-development-for-beginners-2010-edition/#comments</comments>
		<pubDate>Thu, 21 Jan 2010 04:47:33 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Adventure]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=454</guid>
		<description><![CDATA[So you want to learn iPhone development. Good for you! It&#8217;s an exciting platform with great developer tools and an outstanding community. Let me tell you a bit about the benefits of building an app the right way and show you some great resources to get started. The most important point: If you want a [...]]]></description>
			<content:encoded><![CDATA[<p>So you want to learn iPhone development. Good for you! It&#8217;s an exciting platform with great developer tools and an outstanding community. Let me tell you a bit about the benefits of building an app the right way and show you some great resources to get started.</p>
<p>The most important point: If you want a great application with a user experience you&#8217;ll be proud of, build a native application using Apple-blessed developer tools and resources. Apple&#8217;s developer tools are outstanding applications and the iPhone frameworks provide useful, battle-tested, mature code that lets you do a lot without reinventing the wheel every time you need to build even basic functionality. Best of all, Apple gives their tools away for free.</p>
<p><span id="more-454"></span></p>
<p>Don&#8217;t screw around with stuff like PhoneGap or Adobe&#8217;s Flash-to-iPhone binary whatsit just because it might be a little closer to your comfort zone. You&#8217;ll risk writing an application with a library that gets left in the dust when Apple releases a new version of the SDK with great new features. You&#8217;ll also risk having your application rejected if Apple decides one morning that the tools you used are accessing private methods, running interpreted code or disrespecting some other element of the SDK agreement. Finally, you&#8217;re not going to be working with the same design vocabulary expressed by every popular iPhone application.</p>
<p>With UIKit, Apple hands you an enormous pile of outstanding interface classes that are exceptionally clever, attractive and versatile. They&#8217;re yours to build with and do as you please. Thousands of hours of interface coding that you don&#8217;t have to do, saving you time and allowing you to write an app with an experience that&#8217;s consistent with everything your users will expect on the iPhone.</p>
<p>With Core Data, you get a powerful object modeling framework with built-in persistence. What does that get you? If you&#8217;re building an application, you&#8217;ve got data. Lots and lots of data, sometimes. You&#8217;re going to need a way to define and encapsulate that data so that you can keep track of relationships between different chunks, pass information around the application and store it for later use. This can be tedious, laborious, time-consuming work. Core Data does all of the heavy lifting for you.</p>
<p>For me, those are the biggest freebies you get with approaching iPhone development &#8220;the right way.&#8221; That barely scratches the surface, though, since Apple provides powerful APIs that let you accomplish drawing, manipulating the filesystem, performing network operations, dead-simple threading, and many other useful things.</p>
<p>Hopefully you&#8217;re intrigued. You&#8217;ll need to be, especially if you&#8217;re starting from scratch. There&#8217;s a big chunk of learning you&#8217;ll have to do. It&#8217;s worth it, though. The iPhone is a really fun platform and there&#8217;s something potent about developing for a mobile device that&#8217;s always in your pocket.</p>
<p>Find the section that describes you below and proceed from there for my advice on great learning resources.</p>
<h2>You&#8217;ve never coded anything, ever (or, C-based languages scare you)</h2>
<p>Let&#8217;s start with <a href="http://en.wikipedia.org/wiki/C_(programming_language)">C</a>. C is a venerable language with a long history, born in 1972. C is important background to have for iPhone development, since so many Apple APIs use either C or Objective-C, a related language we&#8217;ll talk about next.</p>
<p style="text-align: center;"><a href="http://www.apress.com/book/view/9781430218098"><img class="aligncenter" title="Learn C on the Mac" src="http://www.apress.com/resource/bookcover/9781430218098?size=medium" alt="" width="125" height="164" /></a></p>
<p>Luckily, Dave Mark has you covered. <em><a href="http://www.apress.com/book/view/9781430218098">Learn C on the Mac</a> </em>assumes no pre-existing experience with any programming language. You&#8217;ll get a friendly explanation of everything you need to understand C and the basics of programming. This is ground zero, indispensable knowledge for anyone who is approaching this with no experience. The best part is that Dave has written for someone who will be using Apple&#8217;s developer tools, so you&#8217;ll be getting a perspective that will be useful for your iPhone efforts a little bit later.</p>
<h2>You&#8217;re comfortable with programming and C, but not Objective-C</h2>
<p>Objective-C is built on top of C, so if you&#8217;ve got a basic comfort with programming in C, you&#8217;ll have an easy time getting up to speed. Objective-C lets you build object oriented code. Whuzzat? It&#8217;s Legos for programming. You can build your own bricks and trade them with friends. Put simply, <a href="http://en.wikipedia.org/wiki/Object-oriented_programming">object oriented programming</a> (OOP) lets you build modular software that maximizes code reuse. Reusing code is good! Chances are, if you solve a problem once in your project, you&#8217;ll need that solution again later. OOP lets you easily package up these solutions and deploy them anywhere they&#8217;re needed in your code.</p>
<p style="text-align: center;"><a href="http://www.apress.com/book/view/1430218150"><img class="alignnone" title="Learn Objective-C on the Mac" src="http://www.apress.com/resource/bookcover/9781430218159?size=medium" alt="" width="125" height="164" /></a></p>
<p>Confused? You won&#8217;t be after you read <em><a href="http://www.apress.com/book/view/1430218150">Learn Objective-C on the Mac</a></em>. You&#8217;ll learn all the details of how Objective-C works. The book is packed with clear explanations of how to use object-oriented programming and practical exercises to get you comfortable with building useful code in Objective-C. You will not enjoy learning iPhone development without a solid foundation in Objective-C. You&#8217;ll get one here. Many lightbulbs will turn on for you.</p>
<h2>You understand Objective-C but you&#8217;re not yet confident enough to build an app with it</h2>
<p>If you feel great about Objective-C and you can&#8217;t wait to get started, go on to the next book. If you want to build a little more confidence, it might be worth spending some time practicing with Objective-C and how it interacts with Apple&#8217;s <a href="http://en.wikipedia.org/wiki/Cocoa_(API)">Cocoa</a> API. What&#8217;s Cocoa? It&#8217;s a huge collection of pre-made building blocks you&#8217;ll use to build applications. (Not to be confused with Cocoa Touch, which is a version of Cocoa custom-tailored for the iPhone, which you&#8217;ll meet later.)</p>
<p style="text-align: center;"><a href="http://www.amazon.com/Cocoa-Programming-Mac-OS-3rd/dp/0321503619"><img class="alignnone" title="Cocoa Programming" src="http://t3.gstatic.com/images?q=tbn:OizrTgnl5FY90M%3Ahttp://i41.tinypic.com/xldkdj.jpg" alt="" width="98" height="130" /></a></p>
<p><em><a href="http://www.amazon.com/Cocoa-Programming-Mac-OS-3rd/dp/0321503619">Cocoa Programming for Mac OS X</a> </em>is great introduction to Cocoa. You&#8217;ll learn about using Objective-C with Cocoa, use Interface Builder to construct some interfaces and walk through plenty of exercises. You even get to play with Core Data. By the first dozen chapters or so, you should be ready to rock. Many of the Cocoa goodies you play with on the desktop will be waiting for you on the iPhone.</p>
<h2>You&#8217;re comfortable with programming and Objective-C</h2>
<p>It&#8217;s time to get rolling with the iPhone.</p>
<p style="text-align: center;"><a href="http://www.apress.com/book/view/1430224592"><img class="aligncenter" title="Beginning iPhone Development" src="http://www.apress.com/resource/bookcover/9781430224594?size=medium" alt="" width="125" height="164" /></a></p>
<p>I own several iPhone development books. None is a better, more useful introduction to developing for the platform than <a href="http://www.apress.com/book/view/1430224592"><em>Beginning iPhone Development</em></a>. It&#8217;s Dave Mark again, alongside Jeff LaMarche, with everything you need to get started. <em>Beginning iPhone Development </em>is a comprehensive exploration of building software for the iPhone. The authors provide friendly guidance as they break you in gently. You&#8217;ll learn how to construct interfaces, master table views, and how to store and load data, even using Core Data. The book is filled with useful, practical exercises that you&#8217;ll be able to use for your own projects. Dave and Jeff are great about providing code that is both understandable for learning and yet practical enough to serve as a springboard for solving your own problems in your own apps.</p>
<p>If you&#8217;re learning how to develop for the iPhone but you don&#8217;t have their book, you&#8217;re doing it wrong.</p>
<p>They don&#8217;t stop, there, either.</p>
<p style="text-align: center;"><a href="http://www.apress.com/book/view/143022505x"><img class="aligncenter" title="More iPhone Development" src="http://www.apress.com/resource/bookcover/9781430225058?size=medium" alt="" width="125" height="165" /></a></p>
<p>Jeff and Dave&#8217;s latest, <a href="http://www.apress.com/book/view/143022505x">More iPhone Development</a>, is loaded with meaty chapters on advanced topics. My favorites: several chapters on Core Data (nearly half the book) and an outstanding tutorial on how to use Xcode&#8217;s debugging tools.</p>
<p>The breadth and depth of these titles together is unbeatable for a beginner iPhone developer. Between both books, you&#8217;ll have answers for most quandaries you&#8217;re likely to encounter in your early projects.</p>
<h2>On teaching yourself</h2>
<p>So how do you use these great learning tools? I&#8217;m a passionate, lifetime autodidact. Here&#8217;s my advice:</p>
<p>At least one hour, every single day.</p>
<p>The books I&#8217;ve described present their content split up into chapters. Read and work through at least one chapter per day. Don&#8217;t try to cram it all into a couple of weekends, skimming examples and reading exercises instead of trying them.</p>
<p>Even if you&#8217;re starting from the ground up, you can easily get through every single book I&#8217;ve described here within three or four months.</p>
<p>You&#8217;re setting up your own little school. Make a point of setting a time when your studies begin and end and make a habit of sticking to that schedule. If you have to miss a night of your programming school, give yourself some time to make up for it the next day.</p>
<h3>Equipment</h3>
<p>Get the PDF eBook editions of the books you need. You can never lose them, forget them at the office, spill coffee on them, whatever. They&#8217;re instantly searchable and much easier to work with in a nightly home school setting than paper books. They&#8217;re sometimes cheaper than paperbacks, you don&#8217;t pay shipping and you can get them instantly via the web. I have a blend of paperbacks and PDFs &#8212; I&#8217;m much happier with my PDFs, especially when traveling.</p>
<p>If you&#8217;re serious about programming, you&#8217;re going to need a second monitor as well, unless the one you already have is enormous. One day you&#8217;ll have real projects to work with, which means a debugger window, some documentation open at all times, and plenty of other clutter. A second display will make your life manageable. During your programming school, having a second display for your eBooks will make working through your exercises on the main monitor much easier.</p>
<p>Finally, you&#8217;ll need a notebook. As you work through books and exercises, make note of the important, central concepts and create lists of basic vocabulary. You&#8217;re going to have a lot of new knowledge to sift through. Make it easy on yourself by building your own cheat sheets to reference in the middle of exercises or when you make the jump to the next level of learning. It&#8217;s a lot easier to consult your own notes than try to figure out which book had which handy tip.</p>
<h2>Start</h2>
<p>Just get started. Pick a book, pick a time and start. It&#8217;ll be weird at first, especially if you&#8217;re new to programming. That&#8217;s okay. <a href="http://en.wikipedia.org/wiki/Main_Page">Wikipedia</a> is close by with a wealth of technical articles on anything you might want to know about. If you have questions and don&#8217;t know who to ask, <a href="http://stackoverflow.com/">Stack Overflow</a> is filled with knowledgeable (and not so knowledgeable) members who will be eager to point you in the right direction, no matter how specific or arcane the query.</p>
<p>Good luck! This is an exciting time to develop for mobile devices. The iPhone is the most potent and exciting handheld computer ever. You&#8217;re going to have a great time &#8212; once you get started.</p>
<p style="padding-left: 30px; text-align: right;"><em>(Note: <a href="http://www.apress.com/info">Apress</a> didn&#8217;t sponsor this post or anything but I notice they make up almost all of my recommendations. Kudos to them for hiring outstanding writers who care about relating their wisdom in a clear, enjoyable manner. Their books have great layout and intelligent structure, so they work as well for reference as they do for sequential, hands-on learning. Truly a great publisher who understands the needs of its audience.)</em></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2010/01/20/iphone-development-for-beginners-2010-edition/feed/</wfw:commentRss>
		<slash:comments>10</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>3</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[Stuff I Like]]></category>
		<category><![CDATA[iPhone]]></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>0</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[Neurotic]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[iPhone]]></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"><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"><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>1</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[UI]]></category>
		<category><![CDATA[iPhone]]></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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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>
		<item>
		<title>SwedeShop</title>
		<link>http://blog.danilocampos.com/2009/08/16/swedeshop/</link>
		<comments>http://blog.danilocampos.com/2009/08/16/swedeshop/#comments</comments>
		<pubDate>Sun, 16 Aug 2009 07:25:41 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=311</guid>
		<description><![CDATA[I remember the first time I went to IKEA. It blew my brain open. It&#8217;s the coolest retail experience in the history of the world. There&#8217;s only one problem: you gotta keep track of a lot of data if you want to get out of there with everything you wanted to take home. Enter SwedeShop, [...]]]></description>
			<content:encoded><![CDATA[<p>I remember the first time I went to IKEA.</p>
<p>It blew my brain open. It&#8217;s the coolest retail experience in the history of the world.</p>
<p>There&#8217;s only one problem: you gotta keep track of a lot of data if you want to get out of there with everything you wanted to take home.</p>
<p>Enter <a href="http://www.danilocampos.com/apps/swedeshop/">SwedeShop</a>, my unofficial IKEA shopping list app. Find out more at the main site:</p>
<p><a href="http://www.danilocampos.com/apps/swedeshop/">http://www.danilocampos.com/apps/swedeshop/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/08/16/swedeshop/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The Gravest Pain of an iPhone Developer</title>
		<link>http://blog.danilocampos.com/2009/08/06/the-gravest-pain-of-an-iphone-developer/</link>
		<comments>http://blog.danilocampos.com/2009/08/06/the-gravest-pain-of-an-iphone-developer/#comments</comments>
		<pubDate>Fri, 07 Aug 2009 01:13:20 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Business]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[customer service]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=299</guid>
		<description><![CDATA[It&#8217;s a chattery time for App Store problems. Apple rejected Google Voice, then neutered Ninjawords and still presents an utterly opaque face to developers. There are a laundry list of problems facing the growth of the App Store. I won&#8217;t bother to rehash them here. Let&#8217;s focus on the one that most thoroughly jeopardizes the [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s a chattery time for App Store problems. Apple <a href="http://money.cnn.com/news/newsfeeds/articles/djf500/200907311912DOWJONESDJONLINE000919_FORTUNE5.htm">rejected Google Voice</a>, then <a href="http://daringfireball.net/2009/08/phil_schiller_app_store">neutered Ninjawords</a> and still presents an utterly opaque face to developers.</p>
<p>There are a laundry list of problems facing the growth of the App Store. I won&#8217;t bother to rehash them here. Let&#8217;s focus on the one that most thoroughly jeopardizes the future of developer businesses: Customer Service. Every other problem can be overcome or worked around but without the power of caring for your customers, your business has no reason to exist.</p>
<p>In an aside to a <a href="http://daringfireball.net/linked/2009/07/29/duncan-app-store">link</a> last month, John Gruber muses:</p>
<blockquote><p>I’m wondering how much of the problem is that the App Store is built on the foundation and framework of the iTunes Music Store, which was designed from the outset specifically as a venue for selling 99-cent downloads.</p></blockquote>
<p>This is the most crucially important point: the iTunes Store was never designed to sell software. Among other things, Craig Hockenberry <a href="http://furbo.org/2009/07/10/year-two/">enumerates all the ways</a> in which the App Store is hobbled by this historical truth. It&#8217;s a good, important post that you should read if you care about this kind of stuff. But it doesn&#8217;t address long-term outcomes related to customer service that will doom the developer community.</p>
<p>As an iPhone developer, I have no control over my storefront – Apple manages it for me, with basic data I provide. On the one hand, this is incredible news: access to a huge pool of customers, a complete distribution infrastructure and – best of all – I never have to worry about payment processing.</p>
<p>There&#8217;s just one issue: Apple doesn&#8217;t give a damn about my relationship with my customers.</p>
<p>Generous, attentive, impassioned customer service is an important piece of any successful business. My customers mean the world to me. Unfortunately, iTunes does not provide a clear, encouraging feedback channel.</p>
<h3>User Reviews</h3>
<p>When you&#8217;re selling music, user reviews are a simple tool. Much is subjective, but overall quality will be reflected in the reviews.</p>
<p>With software, the reviews have become more complicated. The most tantalizing way for a customer to speak out about software that is giving them problems is to write a review. And that&#8217;s what they do. Bug reports, feature requests and anything else that comes into their minds gets dumped into the reviews. And why not? The ability to write a review is prominently featured and uses a built-in, official form. It&#8217;s infinitely more seductive than leaving iTunes to write an email to the support contact. It&#8217;s also a venue provided by the same service that is taking the customer&#8217;s money, so it feels more intimately linked to their purchase than anything they can do on an external website or in their email client.</p>
<p>This is infuriating since the communication is strictly one-sided. There&#8217;s no way for the developer to follow up on these reviews to ask for more information. Without that information, acting on a bug report is often impossible. The worst part is that without dialogue, it&#8217;s impossible for the customer to learn more about their problem, discover workarounds and discover that there&#8217;s a living, breathing person who truly cares about the quality of the software they&#8217;ve just purchased.</p>
<p>Like it or not, the iTunes user review becomes the support form of last resort.</p>
<h3>The Consequence</h3>
<p>There are ways around this. <a href="http://www.tap4help.com/">Tap4Help</a> is an interesting example, providing a built-in feedback and support request system. Developers, <a href="http://twitter.com/luciuskwok/status/3170948495">like Lucius Kwok</a>, report some success explicitly declaring their email right in their application description with a call to action encouraging its use. I do this, too, but it doesn&#8217;t catch them all.</p>
<p>Why not? Nothing will ever come close to the power and authority of iTunes itself. I theorize that part of the reason so many customers prefer the review form to using a support email or link is that they know that iTunes will provide them satisfaction. No matter what, iTunes will show their review. They will be heard.</p>
<p>By keeping these customers so thoroughly at arm&#8217;s length, Apple retards the formation of relationships that will build developers&#8217; business. I&#8217;ve turned angry emails into loyal customers through the power of honesty and genuine interest in customer issues. I&#8217;d desperately love to provide that dialogue for every customer, ever, but iTunes, under the current system, will continue to siphon off some portion of those opportunities into its black hole of customer reviews.</p>
<p>Having good conversations with your customers is as essential and non-negotiable as having an engine in your car. When Zappos tweets at me in thanks for my praise, I feel as though my relationship with the company has been further validated. When Netflix gives me complete and generous support when I have trouble with their service, I feel respect for them, since their conduct conveys respect for my business.</p>
<p>It&#8217;s all about how the customer feels. If you never get to talk with them, you&#8217;ll never get to impact that feeling.</p>
<h3>Let&#8217;s Do It Better</h3>
<p>This is not a hard problem to solve. If you happen to work on the iTunes Store infrastructure team, you may feel differently, but the company you work for is in the business of accomplishing the impossible on a fairly regular basis. My sympathy is limited.</p>
<h4>Developer Review Replies</h4>
<p>This is the easiest part. Let the developer reply to user reviews. This isn&#8217;t groundbreaking and I&#8217;m the eight thousandth developer to suggest it. So make it happen. The developer can join the conversation and solicit additional information so that bug reports that go into the reviews can actually be productive. Notify whomever left the review that they have a response via email. For bonus points, let the customer reply directly to that notification to reach the developer.</p>
<h4>Feedback/Support Form</h4>
<p>Let the user provide feedback or support requests through an official, iTunes-embedded form. Send the feedback to the developer via email, with an anonymized reply-to address, like craigslist uses, so Apple can cover their ass on privacy concerns. For bonus points, provide a rating for each application that states how responsive each developer is to requests sent via this form.</p>
<p>There is no step three. With those two provisions, an open dialogue has been created for anyone who bothers to seek one. Software, even for the iPhone, is not music. The one-sided echo-chamber conversation of the iTunes Music Store does not work in the App Store. With the two modest tools I&#8217;ve described, developers will have an infinitely easier time creating the relationships they need to build their business.</p>
<p>I&#8217;m not going to hold my breath. Hopefully Apple is working on this stuff, but in the meantime, I need to figure out better ways to put myself in easy reach of my customers.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/08/06/the-gravest-pain-of-an-iphone-developer/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>No boss, No paycheck, No worries</title>
		<link>http://blog.danilocampos.com/2009/07/26/no-boss-no-paycheck-no-worries/</link>
		<comments>http://blog.danilocampos.com/2009/07/26/no-boss-no-paycheck-no-worries/#comments</comments>
		<pubDate>Sun, 26 Jul 2009 08:13:21 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Adventure]]></category>
		<category><![CDATA[Business]]></category>
		<category><![CDATA[Decisions]]></category>
		<category><![CDATA[Musings]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[self-indulgent]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=287</guid>
		<description><![CDATA[I&#8217;ve been collecting a paycheck since I was 15. It began at Publix, the best damned supermarket you&#8217;ll ever visit. I was a shy kid, reluctant to be employed and encouraged by a dramatically unstable home life to stay as hidden from the world as possible. But I went. I interviewed.  I didn&#8217;t know much [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been collecting a paycheck since I was 15. It began at <a href="http://en.wikipedia.org/wiki/Publix">Publix</a>, the best damned supermarket you&#8217;ll ever visit. I was a shy kid, reluctant to be employed and encouraged by a dramatically unstable home life to stay as hidden from the world as possible. But I went. I interviewed.  I didn&#8217;t know much about interviewing at that point. The myriad job hunting bullet points had yet to be delivered to my brain. I don&#8217;t remember what I said or even what I was asked. It wasn&#8217;t an impressive performance, surely.</p>
<p>But they called me. I had a job.</p>
<p>And I loved it. I&#8217;d never had more fun in my life. Thanks to a handful of adult mentors, I went from being shy and insecure in front of strangers to being outgoing, helpful and outrageously courteous, as befitted Publix&#8217;s customer service mission.  I got to meet people, learn about their lives and help make their day better, all in the time it took to bag up an order and pack in a car. Publix has a firm &#8220;no tipping!&#8221; policy and this was spelled out on a button affixed to my apron at all times. Despite this, not a week went by where a kindly retiree or harried but grateful parent didn&#8217;t stuff a couple bucks into my hand or pocket, buying me a sandwich or drink to end my shift. With a home life that was terrifyingly unpredictable and school that was tedious and unsatisfying, Publix, the people and the tangible benefits of my work there, became an escape that I craved.</p>
<p>There was plenty of reward in the fun of the job, but I found that throwing myself into my work with such gusto had other perks. When all of the front service clerks got reviews, there was much kvetching in the break room. Nickels and dimes, my teenaged colleagues moaned. They barely gave them anything for a raise. When my turn came, my boss, Mr. Starkey, called me into his office. After rattling through his estimate of my performance, I was given a fifty cent raise. It was the largest, Starkey confided, that anyone in my group had gotten. In retrospect, too, I realize that I was rarely tapped to do cleaning chores, since my management seemed to prefer me in front of customers as much as possible.</p>
<p>It was all so perfectly Randian, in a way that satisfied my then-Randroid brain. I gave honest effort in exchange for honest reward and recognition. Love your work, I thought as I pushed a pile of carts back into the store, and nothing feels like work.</p>
<p>Of course, it wouldn&#8217;t last. Home, as was its wont, took another lolloping, staggering jolt. For the second time in less than a year, we were moving away. Mr. Starkey was crestfallen. He&#8217;d been eager to groom me into cashiering and beyond. These were remarks that were and remain deeply flattering – it didn&#8217;t seem like he especially enjoyed terribly many of the other kids who had my title. At my request, he eagerly typed up a letter of recommendation. My favorite line, then and now:</p>
<p>&#8220;I would rehire him immediately if he were to return to Sarasota.&#8221;</p>
<p>I enjoyed it both for the heartfelt endorsement and for the tiny, whimsical implication that I was somehow in control of my existence.</p>
<p>I went on to be a salesman, an intern, a marketing manager and a project manager. With each job, I hoped to find the feeling I knew at Publix. The feeling of throwing myself into my work, enjoying every minute, and always hungry for more.</p>
<p>To be sure, I had some amazing jobs in the years since. Tremendous opportunities that provoked growth and change. But none of it could ever recapture the lost innocence of that first, magical time I worked at the supermarket. This realization, each time I started a new gig, was always a tiny disappointment.</p>
<p>For almost a decade, I&#8217;ve drawn a paycheck from someone. Until now. Not having been to <em>the office, </em>or any office, feels vaguely like retirement. Except there&#8217;s a ton of work to do.</p>
<p>And it&#8217;s back: that magic Publix feeling.</p>
<p>I love my new job. I&#8217;ve spent the last week building a new iPhone app from scratch. My new boss, me, really likes how it turned out. This is the most incredibly rewarding productive activity I have ever chosen for myself. The app is about done; I&#8217;ll have more to say about it soon. The most tremendous and powerful discovery came through its creation: I love developing applications for the iPhone. I can do it all day and night until my fingers hurt and still want more. It&#8217;s the most satisfying thing I&#8217;ve ever invested my working time doing. All I want is to get better and keep building.</p>
<p>Like Publix ten years ago, it doesn&#8217;t feel like work. It&#8217;s fun. It&#8217;s&#8230; wonderful.</p>
<p>Time will tell if this feeling and the products it creates will be sufficient to feed and house me. For now, I&#8217;ve got enough to hold out for awhile and give it everything I&#8217;ve got.</p>
<p>It&#8217;s a scary prospect to abandon security and regular cashflow, move across the country, and go into business for yourself, all the while hoping to hell everything will work out okay. Like many projects, it&#8217;s one of those things where if you truly took the time to consider all the attendant difficulty, complication and risk, you&#8217;d never bother to do it all.</p>
<p>It&#8217;s the best decision I&#8217;ve ever made.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/07/26/no-boss-no-paycheck-no-worries/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>GlobeJot fixed, waiting on approval.</title>
		<link>http://blog.danilocampos.com/2009/06/19/globejot-fixed-waiting-on-approval/</link>
		<comments>http://blog.danilocampos.com/2009/06/19/globejot-fixed-waiting-on-approval/#comments</comments>
		<pubDate>Fri, 19 Jun 2009 14:16:38 +0000</pubDate>
		<dc:creator>Danilo Campos</dc:creator>
				<category><![CDATA[Business]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://blog.danilocampos.com/?p=271</guid>
		<description><![CDATA[The App Store approval delay is easily the most painful and difficult part of developing for iPhone: The good news is that nearly two weeks ago, GlobeJot&#8217;s major issues had been diagnosed and corrected. The bad news is that, until Apple gives the go-ahead, the fixed version is not available for your enjoyment. Learn more [...]]]></description>
			<content:encoded><![CDATA[<p>The App Store approval delay is easily the most painful and difficult part of developing for iPhone:</p>
<blockquote><p>The good news is that nearly two weeks ago, GlobeJot&#8217;s major issues had been diagnosed and corrected. The bad news is that, until Apple gives the go-ahead, the fixed version is not available for your enjoyment.</p></blockquote>
<p>Learn <a href="http://www.danilocampos.com/2009/06/globejot-101-awaiting-approval/">more at the main site</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.danilocampos.com/2009/06/19/globejot-fixed-waiting-on-approval/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
<!-- WP Super Cache is installed but broken. The path to wp-cache-phase1.php in wp-content/advanced-cache.php must be fixed! -->