<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>xinsight mobile development</title>
 <link href="http://xinsight.ca/blog/atom.xml" rel="self"/>
 <link href="http://xinsight.ca"/>
 <updated>2012-01-24T15:48:23+01:00</updated>
 <id>http://xinsight.ca</id>
 <author>
   <name>Jason Moore</name>
   <email>jmoore@xinsight.ca</email>
 </author>

 
 <entry>
   <title>Alternate App Store promo code download link</title>
   <link href="http://xinsight.ca/blog/app-store-promo-code-link/"/>
   <updated>2012-01-24T00:00:00+01:00</updated>
   <id>http://xinsight.ca/blog/app-store-promo-code-link</id>
   <content type="html">&lt;p&gt;This is an update on an old tip &lt;a href=&quot;http://taptaptap.com/blog/user-friendly-app-store-promo-codes/&quot;&gt;originally documented way back in 2009&lt;/a&gt;. Instead of sending people complicated instructions on how to redeem promo codes (copy the promo code to to clipboard, open the app, go to the Featured tab, scroll down, etc.) just send a link and ask them to open it on their iOS device.&lt;/p&gt;

&lt;p&gt;The original tip used the &amp;ldquo;phobos&amp;rdquo; urls that are starting to look ancient and I&amp;rsquo;m not confident how long they will continue to work. I just tried substituting the URL that is used when you send an app as a gift and sure enough, it works fine.&lt;/p&gt;

&lt;p&gt;The URL format is:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;text&quot;&gt;https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/com.apple.jingle.app.finance.DirectAction/freeProductCodeWizard?code=#CODE#&amp;amp;mt=8
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Where &lt;strong&gt;&lt;code&gt;#CODE#&lt;/code&gt;&lt;/strong&gt; should be replaced with your 12 character long code. (&lt;a href=&quot;https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/com.apple.jingle.app.finance.DirectAction/freeProductCodeWizard?code=#CODE#&amp;amp;mt=8&quot;&gt;Copy this link&lt;/a&gt; if the above URL is too long.)&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you&amp;rsquo;re toying with the idea of moving to &lt;a href=&quot;https://twitter.com/#!/appsterdamrs&quot;&gt;#Appsterdam&lt;/a&gt;, my latest app is a good way to tune your ear to the Dutch language. Available for &lt;a href=&quot;http://bit.ly/wQCeIQ&quot;&gt;iOS&lt;/a&gt; and &lt;a href=&quot;http://bit.ly/A1zs6u&quot;&gt;Android&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Locations App - iOS source code for sale</title>
   <link href="http://xinsight.ca/blog/locations-app-source-code-for-sale/"/>
   <updated>2011-10-25T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/locations-app-source-code-for-sale</id>
   <content type="html">&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/Locations-Icon-512.png&quot; height=&quot;256&quot; width=&quot;256&quot; alt=&quot;Locations App Icon&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Back in 2009, I released an iPhone app that mapped bike store locations. I started with one city and had plans to cover North America, but once I realized how much effort was required to compile the data for a single city, I decided that I would &lt;a href=&quot;/blog/bikefixto-iphone-app-source-code-for-sale/&quot;&gt;sell the source code&lt;/a&gt; to others who had locations already compiled or who were better at monetizing the app to fund the data collection.&lt;/p&gt;

&lt;p&gt;The response was positive. I don&amp;rsquo;t know if anyone actually used it for bike shops, but people used that code to map everything from farmer&amp;rsquo;s markets to muffler repair shops.&lt;/p&gt;

&lt;p&gt;The biggest problem with the code was that it was originally written in the days of iOS 2. (The original version just linked to a static google map, since MapKit wasn&amp;rsquo;t available yet.) I added MapKit support and other features as they became available, but still it needed a rethink and a redesign. The other biggest problem that while it allowed for the data to be updated from within the app, it was a manual process that was too &lt;em&gt;fiddly&lt;/em&gt;. I wanted to make that easier.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href=&quot;/locations/&quot;&gt;&lt;strong&gt;Locations App&lt;/strong&gt;&lt;/a&gt;. It&amp;rsquo;s a generic locations app with a web-based admin tool. If you have a bunch of locations that you want to put in an iOS app, then this is the fastest way to build that app.&lt;/p&gt;

&lt;p&gt;Anyone who purchased the source code to BikeFixTO can have access to Locations App – just get in touch.&lt;/p&gt;

&lt;p&gt;For more details, see &lt;a href=&quot;/locations/&quot;&gt;the Locations App page&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>When to ask for an app review</title>
   <link href="http://xinsight.ca/blog/when-to-ask-for-a-review/"/>
   <updated>2011-09-12T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/when-to-ask-for-a-review</id>
   <content type="html">&lt;p&gt;One of my favourite weather apps is called RadarUS. After using it for a week, every time I would open it I would get this dialog:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/pop-up-rate-radarus.png&quot; title=&quot;Dialog: Rate RadarUS&quot; height=&quot;480&quot; width=&quot;320&quot; alt=&quot;iOS Dialog asking user to review an app&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, I love the app, and I would be happy to give them 5-stars and maybe even a quick review, but this request to review the app is only displayed at launch, which is &lt;strong&gt;exactly&lt;/strong&gt; the wrong time to be ask. I opened the app because I want to use it – not rate it.&lt;/p&gt;

&lt;p&gt;The developer clearly realized that this might not be the right time to ask, so they allow users to dismiss the alert, but have it come back again. But the problem remains, as the app will just ask me later at the exact same wrong time. I&amp;rsquo;ve hit &amp;ldquo;Remind me later&amp;rdquo; probably 30 times now.&lt;/p&gt;

&lt;h3 id=&quot;improvements&quot;&gt;Improvements&lt;/h3&gt;

&lt;p&gt;So, how could the prompt to review be improved? Well, first, there should be a way to review the app when I want to. That could be as simple as a button in &amp;lsquo;help&amp;rsquo; or &amp;lsquo;about&amp;rsquo; screens. But a better solution would be to ask the user to review after they&amp;rsquo;ve used the app to achieve some goal. For example, after they have dismissed a modal weather alert or 10 seconds after the radar animation has played.&lt;/p&gt;

&lt;p&gt;And I would wager that a side effect of asking users to review after they&amp;rsquo;ve done something will not only result in more reviews, but more positive ones.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Tracking keyword ranking and improving findability in the Android Marketplace</title>
   <link href="http://xinsight.ca/blog/tracking-and-improving-findability-in-the-android-marketplace/"/>
   <updated>2011-07-14T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/tracking-and-improving-findability-in-the-android-marketplace</id>
   <content type="html">&lt;p&gt;A few months ago, I ported an existing iOS app to Android. The sales results have been&amp;hellip; lacklustre. Only after months have I cracked the 50-100 downloads category. I mostly chalked this up to the horror stories such as &lt;a href=&quot;http://www.readwriteweb.com/mobile/2011/05/97-percent-of-android-app-downloads-were-free-says-chomp.php&quot;&gt;97% of Android downloads were for free apps&lt;/a&gt; or &lt;a href=&quot;http://www.readwriteweb.com/mobile/2011/05/new-report-examines-download-volume-on-android-market.php&quot;&gt;most paid apps are downloaded less than 100 times&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I recently released a small update and took the opportunity to look a bit closer at the Android Market. I searched for &amp;lsquo;&lt;strong&gt;swedish&lt;/strong&gt;&amp;rsquo; and couldn&amp;rsquo;t find &lt;a href=&quot;http://xinsight.ca/goswedish/&quot;&gt;GoSwedish&lt;/a&gt; in the results at all. The apps that were being displayed weren&amp;rsquo;t particularly remarkable in terms of downloads or ratings, so I realized that perhaps not having the term &amp;lsquo;swedish&amp;rsquo; in the title was killing me.&lt;/p&gt;

&lt;p&gt;I changed the title from &amp;ldquo;GoSwedish&amp;rdquo; to &amp;ldquo;Goswedish - Learn Swedish!&amp;rdquo;. (Not my proudest bit of writing, but hey, there&amp;rsquo;s only 30 characters to work with.) A few hours later, and I was showing up ranked #26.&lt;/p&gt;

&lt;p&gt;Moving from nowhere to #26 isn&amp;rsquo;t bad, but since the search results show 24 apps per page, I&amp;rsquo;m still on the second page. I wanted to know when I made it to the first page, so I wrote a python script with the wonderful web scraping tool &lt;a href=&quot;http://www.crummy.com/software/BeautifulSoup/&quot;&gt;BeautifulSoup&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env python&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;BeautifulSoup&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeautifulSoup&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;re&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;urllib2&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# note: 48 seems to be the maximum results per page&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# if you&amp;#39;re app isn&amp;#39;t in the top 48, then you can adjust&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# where the list starts by adding this to the url: &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# start=48&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;keyword&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;swedish&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;ca.xinsight.goswedish&amp;#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;https://market.android.com/search?q=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;amp;so=1&amp;amp;c=apps&amp;amp;num=48&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keyword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,)&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;urllib2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;urlopen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;soup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeautifulSoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# note: need to use dictionary calling style because of the &amp;#39;-&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;soup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;ul&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;class&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;search-results-list&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# get the li elements&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findAllNext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;li&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;data-docid&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
             &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; is ranked #&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%d&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; for keyword: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keyword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,)&lt;/span&gt;
             &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
             
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Running it produces a result like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; ca.xinsight.goswedish is ranked #26 for keyword: swedish
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to run this script, you should grab &lt;a href=&quot;http://www.crummy.com/software/BeautifulSoup/&quot;&gt;Beautiful Soup&lt;/a&gt;. The install process is simply:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;curl -O http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulSoup-3.2.0.tar.gz
tar xzvf BeautifulSoup-3.2.0.tar.gz 
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;BeautifulSoup-3.2.0
python setup.py install
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And just edit the &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;keyword&lt;/code&gt; variables for your own purposes.&lt;/p&gt;

&lt;p&gt;I&amp;rsquo;ve also set it up to run as a cronjob, so I get an email every morning about how I&amp;rsquo;m doing in the Android Market.&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.readwriteweb.com/mobile/2011/05/97-percent-of-android-app-downloads-were-free-says-chomp.php&quot;&gt;97% of Android App Downloads in April were Free, Says Chomp&lt;/a&gt;, &lt;em&gt;readwriteweb.com&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.readwriteweb.com/mobile/2011/05/new-report-examines-download-volume-on-android-market.php&quot;&gt;Majority of Paid Apps on Android Downloaded Less than 100 Times&lt;/a&gt;, &lt;em&gt;readwriteweb.com&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.crummy.com/software/BeautifulSoup/&quot;&gt;BeautifulSoup&lt;/a&gt; &lt;em&gt;crummy.com&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://xinsight.ca/goswedish/&quot;&gt;GoSwedish - A fun way to learn Swedish&lt;/a&gt; &lt;em&gt;xinsight.ca&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Add copy functionality to a custom UIView</title>
   <link href="http://xinsight.ca/blog/add-copy-funtionality-to-a-custom-uiview/"/>
   <updated>2011-06-24T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/add-copy-funtionality-to-a-custom-uiview</id>
   <content type="html">&lt;p&gt;In the crowded App Store, anything you can do to make your app part of people&amp;rsquo;s online conversations will help spread the word and give your app more exposure. My latest app &lt;strong&gt;&lt;a href=&quot;http://cosmostimer.com/&quot;&gt;Cosmos Timer&lt;/a&gt;&lt;/strong&gt; has a nice countdown for various events, and I wanted people to be able to share that via email or twitter.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/pop-up-copy-menu.jpg&quot; title=&quot;Homebrew is ready in 4 days, 3 hours, 1 minute, 45 seconds&quot; height=&quot;&quot; width=&quot;&quot; alt=&quot;menu with options to copy or tweet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I expected adding a popup menu with the copy feature to be painful or at least difficult, so I procrastinated adding it. But once I got around to tackling it, I was surprised by how straightforward it actually was.&lt;/p&gt;

&lt;h3 id=&quot;enable-long-touch&quot;&gt;Enable Long Touch&lt;/h3&gt;

&lt;p&gt;The convention for bringing up the copy menu is the long touch. With iOS 4 this is as simple as adding a gesture to your custom UIView. (Apple calls it a &amp;ldquo;long press&amp;rdquo;.)&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;initWithFrame:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CGRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ... whatever you create for your custom view goes here ...&lt;/span&gt;
    
  &lt;span class=&quot;n&quot;&gt;UILongPressGestureRecognizer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;hold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UILongPressGestureRecognizer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;initWithTarget:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; 
                                                       &lt;span class=&quot;nl&quot;&gt;action:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doLongTouch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;addGestureRecognizer:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hold&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;A long touch calls the method &lt;code&gt;doLongTouch&lt;/code&gt;. Let&amp;rsquo;s add an option to send a text message (SMS):&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doLongTouch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;becomeFirstResponder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	
  &lt;span class=&quot;n&quot;&gt;UIMenuController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;menu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIMenuController&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sharedMenuController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;NSMutableArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSMutableArray&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MFMessageComposeViewController&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;canSendText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;UIMenuItem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIMenuItem&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;initWithTitle:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;SMS&amp;quot;&lt;/span&gt;
                                                  &lt;span class=&quot;nl&quot;&gt;action:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doSMS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;addObject:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;menu&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setMenuItems:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;menu&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setTargetRect:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CGRectMake&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;nl&quot;&gt;inView:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;menu&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setMenuVisible:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;animated:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;doSMS&lt;/code&gt; method just pops up the standard SMS screen. (You&amp;rsquo;ll need to add the &lt;code&gt;MessageUI.framework&lt;/code&gt; to your project.)&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doSMS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;	
  &lt;span class=&quot;n&quot;&gt;MFMessageComposeViewController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MFMessageComposeViewController&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messageComposeDelegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	
  &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;@&amp;quot;A boring string that should be interesting.&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vc&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;presentModalViewController:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;animated:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The &amp;ldquo;copy&amp;rdquo; option is supported by an informal protocol (see &lt;code&gt;UIResponderStandardEditActions&lt;/code&gt; for details). Just create these methods and it will appear:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;canBecomeFirstResponder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;canPerformAction:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;withSender:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;copy:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;canPerformAction:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;withSender:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;copy:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;@&amp;quot;This should be something more interesting than a static string.&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;	
  &lt;span class=&quot;n&quot;&gt;UIPasteboard&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paste&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIPasteboard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generalPasteboard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;paste&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;persistent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paste&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;setString:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;	
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;To summarize: When the long press gesture is detected, the &amp;ldquo;copy&amp;rdquo; menu option is automatically created if your &lt;code&gt;UIView&lt;/code&gt; has a &lt;code&gt;copy:&lt;/code&gt; method , and you can explicitly add other options to the menu.&lt;/p&gt;

&lt;p&gt;To actually copy the text to the clipboard, you just need to assign an &lt;code&gt;NSString&lt;/code&gt; to the &lt;code&gt;UIPasteboard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Easy. No excuse not to make your next project a little more social.&lt;/p&gt;

&lt;h3 id=&quot;finishing-touches&quot;&gt;Finishing Touches&lt;/h3&gt;

&lt;p&gt;Without any visual feedback, your users won&amp;rsquo;t know that your custom &lt;code&gt;UIView&lt;/code&gt; has any interactivity. You&amp;rsquo;ll want to add these events – hopefully with something more attractive than a red background.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;touchesBegan:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSSet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;touches&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;withEvent:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backgroundColor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIColor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;touchesEnded:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSSet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;touches&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;withEvent:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backgroundColor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIColor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clearColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// note: cancelled called when long touch is detected&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;touchesCancelled:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSSet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;touches&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;withEvent:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backgroundColor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIColor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clearColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;one-limitation&quot;&gt;One Limitation&lt;/h3&gt;

&lt;p&gt;I know I&amp;rsquo;ve seen popup menus with more than three items before, but I was only able to get two to appear before items would be moved onto a second menu which could only be seen by tapping &amp;ldquo;More&amp;hellip;&amp;rdquo;. I found that annoying, so I limited the menu to two items.&lt;/p&gt;

&lt;p&gt;If you happen to know of a workaround to get more than two items in the popup menu, please share how in the comments.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/pop-up-menu-split.png&quot; alt=&quot;Pop Up Menu Split&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;update&quot;&gt;Update&lt;/h4&gt;

&lt;p&gt;Thanks to Jonathan Badeen for pointing out that if you rename &lt;code&gt;copy:&lt;/code&gt; to &lt;code&gt;copySelection:&lt;/code&gt; and then explicitly add a copy menu item, you can can avoid the &amp;ldquo;More&amp;hellip;&amp;rdquo; problem.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;  &lt;span class=&quot;c1&quot;&gt;// note: we call &amp;#39;copySelection:&amp;#39; instead of &amp;#39;copy:&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;UIMenuItem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;copy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIMenuItem&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;initWithTitle:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;Copy&amp;quot;&lt;/span&gt;
                                                &lt;span class=&quot;nl&quot;&gt;action:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;copySelection:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;addObject:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;copy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I was even able to get four items!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/four-popup-menu-options.png&quot; alt=&quot;Four Popup Menu Options&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If you like this post, have a look at &lt;strong&gt;&lt;a href=&quot;http://cosmostimer.com/&quot;&gt;Cosmos Timer&lt;/a&gt;&lt;/strong&gt; - it can track both short and ridiculously long events. It&amp;rsquo;s the perfect place to keep track of things that don&amp;rsquo;t easily fit into a todo list or a calendar.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Using a Repeating NSTimer in a UIViewController</title>
   <link href="http://xinsight.ca/blog/nstimer-in-a-uiviewcontroller/"/>
   <updated>2011-06-09T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/nstimer-in-a-uiviewcontroller</id>
   <content type="html">&lt;p&gt;I often find myself needing a repeating timer as part of a &lt;code&gt;UIViewController&lt;/code&gt;. For example - in my recent app &lt;a href=&quot;http://cosmostimer.com&quot;&gt;Cosmos Timer&lt;/a&gt; on the alert sound selection screen, I wanted to check the mute switch status once a second. Depending on the status, I would show or hide a little warning message.&lt;/p&gt;

&lt;p&gt;I initially had this in my &lt;code&gt;viewDidLoad&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;	
	&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSTimer&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;scheduledTimerWithTimeInterval:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;target:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; 
	                                      &lt;span class=&quot;nl&quot;&gt;selector:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;checkIfMuteIsOn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
	                                      &lt;span class=&quot;nl&quot;&gt;userInfo:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt; 
	                                      &lt;span class=&quot;nl&quot;&gt;repeats:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;retain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;	
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And to clean-up the timer, I had:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewDidUnLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewDidUnload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;   
   &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;invalidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// stop timer&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Seems reasonable, right? Well, no. The view controller&amp;rsquo;s &lt;code&gt;dealloc&lt;/code&gt; method was not being called. Nor was &lt;code&gt;viewDidUnLoad&lt;/code&gt;. So every time I accessed this screen, another repeating timer was being created to poll the state of the mute switch.&lt;/p&gt;

&lt;p&gt;A quick test confirmed this &amp;ndash; removing the &lt;code&gt;NSTimer&lt;/code&gt;, caused the view controller&amp;rsquo;s &lt;code&gt;dealloc&lt;/code&gt; method to be called as normal.&lt;/p&gt;

&lt;p&gt;Looking closer at the docs for &lt;code&gt;scheduledTimerWithTimeInterval:invocation:repeats:&lt;/code&gt; - this line jumped out:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The timer instructs the invocation object to retain its arguments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ah, so the timer adds a &lt;code&gt;retain&lt;/code&gt; to the view controller. And until the &lt;code&gt;NSTimer&lt;/code&gt; is stopped, the view controller will never be dealloc&amp;rsquo;ed or unloaded. But I was stopping the timer in the unload method, so both the view controller and timer would never be destroyed. A circular reference.&lt;/p&gt;

&lt;p&gt;So, what is the best way to add a repeating &lt;code&gt;NSTimer&lt;/code&gt; to a &lt;code&gt;UIViewController&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I settled on this:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;viewWillAppear:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;viewWillAppear:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSTimer&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;scheduledTimerWithTimeInterval:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; 
	                                        &lt;span class=&quot;nl&quot;&gt;target:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;
	                                        &lt;span class=&quot;nl&quot;&gt;selector:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;checkIfMuteIsOn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
	                                        &lt;span class=&quot;nl&quot;&gt;userInfo:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;
	                                        &lt;span class=&quot;nl&quot;&gt;repeats:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;retain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;viewWillDisappear:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;BOOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;viewWillDisappear:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;invalidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;	
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I don&amp;rsquo;t really need the &lt;code&gt;[mute retain]&lt;/code&gt; and &lt;code&gt;[mute release]&lt;/code&gt;, since the &lt;code&gt;NSTimer&lt;/code&gt; retains the target &amp;lsquo;self&amp;rsquo; (i.e. the &lt;code&gt;UIViewController&lt;/code&gt;) and won&amp;rsquo;t be autoreleased until we&amp;rsquo;re done with it - but it&amp;rsquo;s easier to just add the retain/release than to explain this. ;)&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Reflections on Porting an iOS app to Android</title>
   <link href="http://xinsight.ca/blog/porting-an-ios-app-to-android/"/>
   <updated>2011-04-13T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/porting-an-ios-app-to-android</id>
   <content type="html">&lt;p&gt;I wanted some Android development experience, so thought it made sense to port an app where I could re-use most of the content. I recently released &lt;a href=&quot;http://xinsight.ca/goswedish/&quot;&gt;GoSwedish&lt;/a&gt; for Android, and I thought I&amp;rsquo;d note some of the differences between iOS and Android platforms that surprised me.&lt;/p&gt;

&lt;h3 id=&quot;icons&quot;&gt;Icons&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/bad-and-good-android-icon-vert.png&quot; alt=&quot;&quot; title=&quot;Bad and Better Android Icon&quot; width=&quot;132&quot; height=&quot;264&quot; class=&quot;alignright size-full wp-image-66&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Initially I just quickly glanced at the sizing requirements for Android icons and created the low, medium and high resolution versions from same 512x512 PSD that I used for iOS. The icon looked fine on the app screen that had a black background, but when I moved it to the home screen the icon had a black box around it. Terrible.&lt;/p&gt;

&lt;p&gt;After actually reading the spec, I learned that the icons have a transparent background, there are specific requirements for the drop shadow and dead space around the icon. Oh, and they shouldn&amp;rsquo;t be glossy.&lt;/p&gt;

&lt;h3 id=&quot;screenshots&quot;&gt;Screenshots&lt;/h3&gt;

&lt;p&gt;There is no easy way to take a screenshot with Android. You have to connect your device via USB and fire up a java app from the SDK called &lt;code&gt;ddms&lt;/code&gt;. On iOS you simply press the power and home buttons at the same time. This seems like a minor complaint, but only when this feature was missing did I realize how much I relied on screenshots as part of my workflow for making a note of bugs or capturing problems with the layout.&lt;/p&gt;

&lt;h3 id=&quot;interface&quot;&gt;Interface&lt;/h3&gt;

&lt;p&gt;I started development before having actually used an Android device. I didn&amp;rsquo;t actually know how Android apps were supposed to work, so I was trying to make it work like the iOS app. One huge difference with Android between iOS is that not every control is on screen. Most notably, the &lt;strong&gt;Back&lt;/strong&gt; and &lt;strong&gt;Menu&lt;/strong&gt; buttons.&lt;/p&gt;

&lt;p&gt;The interface for GoSwedish is simple: You view a list of lessons and select one to start. Once you&amp;rsquo;re in a lesson, you can go back to the main lesson list at any time. Here&amp;rsquo;s the interface, when you&amp;rsquo;re in a lesson in iOS.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/ios-ss3-1.3.png&quot; alt=&quot;&quot; title=&quot;ios-ss3-1.3&quot; width=&quot;320&quot; height=&quot;460&quot; class=&quot;alignnone size-full wp-image-68&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And here is my initial attempt on Android.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/android-ui-back-button2.png&quot; alt=&quot;&quot; title=&quot;android-ui-back-button2&quot; width=&quot;320&quot; height=&quot;480&quot; class=&quot;alignnone size-full wp-image-80&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I had made my own custom back button, and spent hours (yes, hours!) fiddling with the XML layout to position the buttons. (That&amp;rsquo;s a whole other blog post.) Once I got my hands on an Android device, I realized that the physical buttons are a core part of the interaction style. So, I removed the on screen &lt;strong&gt;Back&lt;/strong&gt; button and exposed the &lt;strong&gt;Back&lt;/strong&gt; and &lt;strong&gt;View all&lt;/strong&gt; functionality in the menu.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/android-screen-final.png&quot; alt=&quot;&quot; title=&quot;android-screen-final&quot; width=&quot;160&quot; height=&quot;240&quot; class=&quot;&quot; /&gt; &lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/android-screen-with-menu-final1.png&quot; alt=&quot;&quot; title=&quot;android-screen-with-menu-final&quot; width=&quot;160&quot; height=&quot;240&quot; class=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(Personally, I still find the physical button interaction to be less intuitive and requires more cognitive load. And the location of the buttons at the &amp;ldquo;back&amp;rdquo; button at the bottom-left of the phone is incredibly awkward, but when in Rome&amp;hellip;)&lt;/p&gt;

&lt;h2 id=&quot;versions&quot;&gt;Versions&lt;/h2&gt;

&lt;p&gt;There are a lot of Android versions out in the wild, and so a key question for any Android dev is, &amp;ldquo;What platform to target?&amp;rdquo; Google recently posted information on the breakdown of devices in their Market. There may be a lot of older 1.6 devices out there, but their owners aren&amp;rsquo;t buying apps.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/chart-devices-accessing-android-market.png&quot;&gt;&lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/chart-devices-accessing-android-market.png&quot; alt=&quot;&quot; title=&quot;chart-devices-accessing-android-market&quot; width=&quot;460&quot; height=&quot;250&quot; class=&quot;alignnone size-full wp-image-73&quot; /&gt;&lt;/a&gt;
&lt;em&gt;Source: &lt;a href=&quot;http://developer.android.com/resources/dashboard/platform-versions.html&quot;&gt;http://developer.android.com/resources/dashboard/platform-versions.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was also advised to not bother with 1.6 as it had problems with large content-heavy apps. It seems Android has an &lt;em&gt;interesting&lt;/em&gt; storage system&amp;hellip;&lt;/p&gt;

&lt;h2 id=&quot;internalexternal-storage&quot;&gt;Internal/External Storage&lt;/h2&gt;

&lt;p&gt;Storage is one of those details that users and developers don&amp;rsquo;t have to think about on iOS. If you have space, the app will install. On Android, you have to be aware of the two storage areas and have to make sure that the smaller internal one doesn&amp;rsquo;t fill up. If it does, you have to move apps to the external storage. But some apps can&amp;rsquo;t or won&amp;rsquo;t run on external.&lt;/p&gt;

&lt;p&gt;This system is annoying, but it also causes problems for apps with several MB of assets. To support 1.6, I would have to download assets from within the app to the external storage. In light of the stats of people using the Market (and writing a download manager sounded like a huge amount of work), I decided to only support 2.1 and higher.&lt;/p&gt;

&lt;h2 id=&quot;piracy&quot;&gt;Piracy&lt;/h2&gt;

&lt;p&gt;Xcode and the AppStore handle all the encryption surrounding an app. There are some annoying hoops to jump through, but once you&amp;rsquo;ve done it a few times, protecting your app against piracy isn&amp;rsquo;t something you need to worry about. (Of course, there are ways to remove the encryption, but I just want to make it easier to pay for an app than to steal it.)&lt;/p&gt;

&lt;p&gt;Android has copy-protection that would only allow an app to be installed on the internal storage. That wouldn&amp;rsquo;t work for an app that was over a few megs, since it would take up all the precious internal storage. But it doesn&amp;rsquo;t matter, because the copy-protection system is now deprecated in favour of a licensing check system. The app will periodically validate that it was purchased from the Market for the current account.&lt;/p&gt;

&lt;p&gt;The Android docs walk you through adding the licensing code – and it&amp;rsquo;s mostly boilerplate. For testing, in the Market admin area you can set various response codes to test that the code works.&lt;/p&gt;

&lt;p&gt;The only major drawbacks are if the licensing server goes down, legitimate users might not be able to run the app. And if I wanted to submit the app to another app store (e.g. Amazon), I would have to remove all the Android Market license check code.&lt;/p&gt;

&lt;h2 id=&quot;animation&quot;&gt;Animation&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2011/04/animation-2-birds.png&quot; alt=&quot;&quot; title=&quot;animation-2-birds&quot; width=&quot;160&quot; height=&quot;128&quot; class=&quot;alignright size-full wp-image-88&quot; /&gt;&lt;/p&gt;

&lt;p&gt;GoSwedish has a simple animation when the audio is playing. Both iOS and Android support animations and the way they do it is representative of their respective approaches.&lt;/p&gt;

&lt;p&gt;In iOS the code is:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;objc&quot;&gt;&lt;span class=&quot;n&quot;&gt;UIImageView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imgAnimate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImageView&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;initWithFrame:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CGRectMake&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;imgAnimate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;animationDuration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;imgAnimate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;animationImages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;arrayWithObjects:&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImage&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;imageNamed:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;f1.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImage&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;imageNamed:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;f2.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImage&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;imageNamed:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;f3.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImage&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;imageNamed:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;f4.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImage&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;imageNamed:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;f5.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIImage&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;imageNamed:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&amp;quot;f6.png&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
			&lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imgAnimate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startAnimation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;In Android, the animation is defined with XML.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;xml&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;animation-list&lt;/span&gt;     
    &lt;span class=&quot;na&quot;&gt;xmlns:android=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;android:oneshot=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;item&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:drawable=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;@drawable/bird_f1&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:duration=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;150&amp;quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;item&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:drawable=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;@drawable/bird_f2&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:duration=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;150&amp;quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;item&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:drawable=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;@drawable/bird_f3&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:duration=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;150&amp;quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;item&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:drawable=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;@drawable/bird_f4&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:duration=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;150&amp;quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;item&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:drawable=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;@drawable/bird_f5&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:duration=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;150&amp;quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;item&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:drawable=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;@drawable/bird_f6&amp;quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;android:duration=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;150&amp;quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;    
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/animation-list&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Assuming the XML file was called &lt;code&gt;birdsing.xml&lt;/code&gt;, then you can assign the animation with:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;ImageButton&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ImageButton&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;findViewById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;buttonMute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setImageResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anim&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;birdsing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Other than the overly verbose XML which I find hard to recall from memory - it&amp;rsquo;s not too bad. But in practice, the animation sometimes wouldn&amp;rsquo;t start when the Activity was first loaded, so this hack was required:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnimationDrawable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frameAnimation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AnimationDrawable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDrawable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
	&lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;frameAnimation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;                
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;            
&lt;span class=&quot;o&quot;&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&quot;naming-resources&quot;&gt;Naming Resources&lt;/h2&gt;

&lt;p&gt;Android provides a mechanism to access the id of any resources in the app. It&amp;rsquo;s a nice hack. You can play an audio file simply with:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;MediaPlayer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MediaPlayer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getApplicationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;woohoo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;However, this also means that filenames can only contain lowercase letters, digits, periods or dashes. I had hundreds of mp3 files, which I didn&amp;rsquo;t want to have to rename from the iOS app. I was able to put them in the &amp;ldquo;assets&amp;rdquo; directory, and then it&amp;rsquo;s just a bit more work to access them.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;java&quot;&gt;&lt;span class=&quot;n&quot;&gt;AssetManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;am&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getAssets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AssetFileDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;am&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;openFd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;mp3/&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;.mp3&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;		&lt;span class=&quot;c1&quot;&gt;// required to set datasource again&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDataSource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getFileDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStartOffset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLength&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;prepare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p class=&quot;center&quot;&gt;• • •&lt;/p&gt;

&lt;p&gt;So those are some of the biggest issues that jumped out at me while developing for Android. There&amp;rsquo;s lots more items that I could mention, but I&amp;rsquo;ll save them for another post. Feel free to ask any specific questions in the comments.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Why In-App-Purchase is a Mistake</title>
   <link href="http://xinsight.ca/blog/why-in-app-purchase-is-a-mistake/"/>
   <updated>2010-11-04T00:00:00+01:00</updated>
   <id>http://xinsight.ca/blog/why-in-app-purchase-is-a-mistake</id>
   <content type="html">&lt;p&gt;&lt;img id=&quot;image60&quot; src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2010/11/iap-disadvantage.png&quot; alt=&quot;iap-disadvantage.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Thinking about using In-App-Purchase (IAP) instead of having separate free and paid apps? My advice: Don&amp;rsquo;t do it.&lt;/p&gt;

&lt;p&gt;In the App Store, the original way to offer a demo was to have a free (&amp;ldquo;lite&amp;rdquo;) version of your app to convince people to buy the paid (&amp;ldquo;full&amp;rdquo;) version. Apple introduced In-App Payments (IAP) with iOS 3.0 for paid apps and a year ago (Oct 2009) allowed it to be used with free apps as well.&lt;/p&gt;

&lt;p&gt;In theory, IAP sounds great. Users download a free app, and if they like it they can buy the full version right from within the app. And developers only have to deal with maintaining a single app instead of a free one and a paid one. Unfortunately, IAP is a disaster in practice. I have built two apps using IAP, so let me show you the scars.&lt;/p&gt;

&lt;h2 id=&quot;poor-ranking&quot;&gt;Poor ranking&lt;/h2&gt;

&lt;p&gt;We all know that being highly ranked in the App Store is key for visibility and sales. The ranking algorithms are secret, but it&amp;rsquo;s clear that paid apps ranking is based on units sold and the gross sales over a period of time. For free apps it is simply units downloaded over a period of time. So, how are free apps with IAP ranked? Unless you have a 100% conversion rate, you will be at a disadvantage being compared with the other paid apps. And since your app isn&amp;rsquo;t a fully free app in the traditional sense (ad supported or is subsidized by another business e.g. skype, twitter) it will have limited functionality and probably won&amp;rsquo;t have same number of downloads. So you are disadvantaged in the free rankings as well. Look at the top 25 free apps, how many use IAP? (At the time of writing, I see 5.)&lt;/p&gt;

&lt;h2 id=&quot;less-shelf-space&quot;&gt;Less &amp;ldquo;shelf space&amp;rdquo;&lt;/h2&gt;

&lt;p&gt;With a free and a paid app, you have two products - and each one shows up on the free and paid lists. The App Store defaults to showing &amp;ldquo;paid&amp;rdquo; apps, so with IAP, you are only on one list and you&amp;rsquo;re not on the default list.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;image61&quot; src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2010/11/iap-montager-free-paid.jpg&quot; alt=&quot;iap-montager-free-paid.jpg&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;wrong-kind-of-customers&quot;&gt;Wrong kind of customers&lt;/h2&gt;

&lt;p&gt;Yes, you need customers, but you also need the right kind of customers. From my experience, people who only download free apps are more likely to gripe that $1.99 is expensive or demand features x,y,z before they pay. But they&amp;rsquo;ll never pay. Downloading and installing apps is just something to do, like flipping through channels on the TV.&lt;/p&gt;

&lt;p&gt;The freebie folks also have no real investment in the app. If they download it and don&amp;rsquo;t see the immediate use in 3 seconds, they will delete it and leave a 1-star rating. This would still happen with the free and paid app scenario, but then at least the 1-star ratings wouldn&amp;rsquo;t drag down the average rating of your paid app.&lt;/p&gt;

&lt;h2 id=&quot;no-gifting&quot;&gt;No gifting&lt;/h2&gt;

&lt;p&gt;You can&amp;rsquo;t give an IAP as a gift. Don&amp;rsquo;t expect a bump in sales at Christmas.&lt;/p&gt;

&lt;p&gt;(Gifting is also a handy promo code substitute for developers outside of the US.)&lt;/p&gt;

&lt;h2 id=&quot;iap-is-more-complicated-for-users&quot;&gt;IAP is more complicated for users&lt;/h2&gt;

&lt;p&gt;Even if the IAP interface is just some text, a buy button, a busy indicator, and a status message, users will have to learn how to use each developer&amp;rsquo;s IAP interface. Why introduce this friction when they already know how to buy in the App Store?&lt;/p&gt;

&lt;h2 id=&quot;iap-is-added-complexity-for-developers&quot;&gt;IAP is added complexity for developers&lt;/h2&gt;

&lt;p&gt;Apple provides a nice StoreKit API and lots of boilerplate sample code you can just paste into your app, but it still adds complexity. First, as mentioned above, you need to build a little interface to show the description and some status messages and find a place for it in your app. You also have to store the fact that they&amp;rsquo;ve upgraded (e.g. isPro=1) and elegantly handle the transition from free and the paid states (e.g. add items to toolbars). Testing is also extra work, since you need to create iTunes test accounts and you definitely want to make sure you that the upgrade process still works after any major code changes.&lt;/p&gt;

&lt;p&gt;You also want to make sure that you actually sell the upgrade. If you aren&amp;rsquo;t making it &lt;strong&gt;blindingly obvious&lt;/strong&gt; how to upgrade (and what you get) then it is too subtle.&lt;/p&gt;

&lt;h2 id=&quot;iap-lock-in&quot;&gt;IAP lock-in&lt;/h2&gt;

&lt;p&gt;Once you&amp;rsquo;ve gone the IAP route, you are stuck. You can&amp;rsquo;t rip out IAP and start charging for your app without giving it away to all the people who downloaded it for free. And you can&amp;rsquo;t make a new paid app without abandoning the people who already paid via IAP.&lt;/p&gt;

&lt;p&gt;The only reasonable path away from IAP that I can think of is to make a paid version, remove IAP from the free version (but continue to provide the pro features to those who have paid) and then continue developing and supporting both apps. The main drawback is that once an IAP customer deletes the app, they wouldn&amp;rsquo;t be able to use IAP to upgrade and would be forced to purchase the paid version to get pro features. And hopefully Apple won&amp;rsquo;t consider the storing pro status (isPro=1) a forbidden easter egg.&lt;/p&gt;

&lt;h2 id=&quot;less-control-over-iap-descriptions&quot;&gt;Less control over IAP descriptions&lt;/h2&gt;

&lt;p&gt;You can update an app description anytime, however there are some odd restrictions around updating an IAP product description. In iTunes Connect, you can edit the text, but it awaits approval - and approval only comes when a new version of the app is released. End result: You can&amp;rsquo;t tweak the description to see which is more effective at driving sales  (until you push out a full update).&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;• • •&lt;/p&gt;

&lt;p&gt;Well, I think that about covers it. &lt;/p&gt;

&lt;p&gt;If you like this post, why don&amp;rsquo;t you download my photo montage tool &lt;b&gt;&lt;u&gt;&lt;a href=&quot;http://itunes.apple.com/us/app/id359492273?mt=8&quot;&gt;Montager&lt;/a&gt;&lt;/u&gt;&lt;/b&gt; [itunes] (it&amp;rsquo;s free!) and give it 5-stars.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Xcode Trick - Shortcut to duplicate a line of code</title>
   <link href="http://xinsight.ca/blog/xcode-trick-creating-a-shortcut-to-duplicate-a-line/"/>
   <updated>2010-05-10T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/xcode-trick-creating-a-shortcut-to-duplicate-a-line</id>
   <content type="html">&lt;style type=&quot;text/css&quot; media=&quot;screen&quot;&gt;
  span.button { padding: 4px; background-color: #eee; border: solid 1px #ccc; }
&lt;/style&gt;

&lt;p&gt;One feature I miss in Xcode is the ability to duplicate a line of text with a simple two-key combo. Sure, I could select the line and then copy and paste, but it&amp;rsquo;s far too fiddly. For example, let&amp;rsquo;s say I wanted to add another line to this array:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://xinsight.ca/blog/images/Xcode-duplicate-line-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I would have to type:&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;button&quot;&gt;⌘&lt;/span&gt; + &lt;span class=&quot;button&quot;&gt; ←&lt;/span&gt; move to start of line&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;button&quot;&gt;⇧&lt;/span&gt; + &lt;span class=&quot;button&quot;&gt;↓&lt;/span&gt; (shift-down) select line&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;button&quot;&gt;⌘&lt;/span&gt; + &lt;span class=&quot;button&quot;&gt;C&lt;/span&gt; (copy)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;button&quot;&gt;↓&lt;/span&gt; (down) (deselect)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;button&quot;&gt;⌘&lt;/span&gt; + &lt;span class=&quot;button&quot;&gt;V&lt;/span&gt; (paste)&lt;/p&gt;

&lt;p&gt;And I would end up with this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://xinsight.ca/blog/images/Xcode-duplicate-line-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That&amp;rsquo;s &lt;strong&gt;far&lt;/strong&gt; too much work.&lt;/p&gt;

&lt;p&gt;Digging through Xcode&amp;rsquo;s preferences I couldn&amp;rsquo;t find anything that would help, but then I stumbled on a page called &lt;a href=&quot;http://www.hcs.harvard.edu/~jrus/Site/cocoa-text.html&quot;&gt;Customizing the Cocoa Text System&lt;/a&gt; that showed me how this could work.&lt;/p&gt;

&lt;p&gt;So, I created a directory:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;mkdir ~/Library/KeyBindings
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And created the file: &lt;code&gt;~/Library/KeyBindings/DefaultKeyBinding.dict&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;js&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&amp;quot;^d&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;moveToEndOfLine:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;deleteToBeginningOfLine:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// line in kill buffer&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;yank:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// put back what was deleted&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;insertLineBreak:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;moveToBeginningOfLine:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&amp;quot;yank:&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// duplicate line  &lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Restarted Xcode, and now a ^d (control d) duplicates the line. And I can easily add the new element to the array.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://xinsight.ca/blog/images/Xcode-duplicate-line-3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you want to experiment with your own tweaks, there is &lt;a href=&quot;http://www.hcs.harvard.edu/~jrus/Site/selectors.html&quot;&gt;a list of all selectors&lt;/a&gt;. You&amp;rsquo;ll need to restart Xcode after each change to test your changes. I ran into a small problem with Xcode doing auto-intent after i added a newline with insertNewline:. Luckily, insertLineBreak has the same effect, but avoids the problem of extra whitespace getting added on the duplicated line.&lt;/p&gt;

&lt;p&gt;If you prefer to download the file: &lt;a href=&quot;http://xinsight.ca/blog/files/DefaultKeyBinding.dict.txt&quot;&gt;http://xinsight.ca/blog/files/DefaultKeyBinding.dict.txt&lt;/a&gt; &lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A long road to a simple solution</title>
   <link href="http://xinsight.ca/blog/a-long-road-to-a-simple-solution/"/>
   <updated>2010-01-05T00:00:00+01:00</updated>
   <id>http://xinsight.ca/blog/a-long-road-to-a-simple-solution</id>
   <content type="html">&lt;p&gt;The first iPhone app I wrote was &lt;a href=&quot;/bignames/&quot;&gt;BigNames&lt;/a&gt; - a large-text contact list. It&amp;rsquo;s also one of the apps that I use the most, so I was surprised to hear that a particular user found that it worked the first time, but then crashed on subsequent loads.&lt;/p&gt;

&lt;p&gt;The only thing I could think of was that the app was running out of memory and was being killed by the OS. BigNames builds an array of names when it first loads, so that it can load quickly in the future. A huge number of contacts might consume too much memory. This user had around 6700 (!!!) contacts â€“ far more than I had ever tested â€“ but still I would have expected to load hundreds of thousands of strings into an NSArray before hitting the 20-30 MB memory limit.&lt;/p&gt;

&lt;p&gt;To replicate the problem, I wrote a method to create thousands of bogus contacts. Sure enough, when there were more than 4000 names being loaded from cache, it would crash on load. One odd point, was that that app would only crash when I &lt;strong&gt;wasn&amp;rsquo;t&lt;/strong&gt; running it in debug mode. This was annoying, since normally I would add NSLog messages or breakpoints to identify and understand a problem, but the app would only crash when these tools were unavailable.&lt;/p&gt;

&lt;p&gt;I remembered that Crash Logs are stored on the device and can be viewed with Xcode&amp;rsquo;s Organizer.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;image59&quot; src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2010/01/organizer-crash-logs-450x.png&quot; alt=&quot;organizer-crash-logs-450x.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was able to see that the crashes weren&amp;rsquo;t the normal EXC_BAD_ACCESS variety but was something called 0x8badf00d (ha ha, get it?) and was actually the launchd complaining that the app was &amp;ldquo;failed to launch in time&amp;rdquo;. So, we aren&amp;rsquo;t actually taking about a crash, just a slow to load app.&lt;/p&gt;

&lt;p&gt;And that explains why this wasn&amp;rsquo;t happening during a debug session. Debugging is slower than simply running an app, so I assume that launchd skips the timeout check when in debug mode.&lt;/p&gt;

&lt;p&gt;The &amp;ldquo;failed to launch in time&amp;rdquo; error led me to think that I was doing too much work in &lt;tt&gt;applicationDidFinishLaunching:&lt;/tt&gt;. I moved some of the loading of cached names to a separate thread so the app was able to load without getting killed by launchd â€“ but then was unresponsive for the first 10-15 seconds. Not a huge improvement.&lt;/p&gt;

&lt;p&gt;I realized that the problem was some code was taking far too long to load. It appeared to be UITableView reloadData. But since that is an API call - I couldn&amp;rsquo;t step through Apple&amp;rsquo;s code to see what it was doing. But I did know what the app was doing when it was killed by launchd. The stack trace from the Crash Log was:&lt;/p&gt;

&lt;pre style=&quot;height: 150px; overflow: scroll; width: 480px&quot;&gt;
Thread 0:
0   Foundation                   0x0005d9e0 -[_NSIndexPathUniqueTree uniqueIndexPath:withIndexes:count:] + 132
1   Foundation                   0x0005db8e -[NSIndexPath initWithIndexes:length:] + 86
2   Foundation                   0x0005d6ce +[NSIndexPath indexPathWithIndexes:length:] + 126
3   UIKit                        0x000a2ed8 +[NSIndexPath(UITableView) indexPathForRow:inSection:] + 40
4   UIKit                        0x0009fe84 -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 2052
5   UIKit                        0x0009f5a0 -[UITableViewRowData rectForFooterInSection:] + 96
6   UIKit                        0x000c1380 -[UITableViewRowData rectForTableFooterView] + 168
7   UIKit                        0x001596ac -[UITableView setTableFooterView:] + 240
8   BigNames                     0x000043ea -[ContactTableViewController loadView] (ContactTableViewController.m:103)
9   UIKit                        0x00069750 -[UIViewController view] + 44
10  UIKit                        0x00088fd8 -[UIViewController contentScrollView] + 24
11  UIKit                        0x00088d90 -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] + 36
12  UIKit                        0x00088c3c -[UINavigationController _layoutViewController:] + 28
13  UIKit                        0x0008863c -[UINavigationController _startTransition:fromViewController:toViewController:] + 504
14  UIKit                        0x000883a8 -[UINavigationController _startDeferredTransitionIfNeeded] + 256
15  UIKit                        0x00088298 -[UINavigationController viewWillLayoutSubviews] + 12
16  UIKit                        0x0006c86c -[UILayoutContainerView layoutSubviews] + 76
17  UIKit                        0x000482d0 -[UIView(CALayerDelegate) _layoutSublayersOfLayer:] + 32
18  QuartzCore                   0x0000c1b8 -[CALayer layoutSublayers] + 80
19  QuartzCore                   0x0000bed4 CALayerLayoutIfNeeded + 192
20  QuartzCore                   0x0000b83c CA::Context::commit_transaction(CA::Transaction*) + 256
21  QuartzCore                   0x0000b46c CA::Transaction::commit() + 276
22  QuartzCore                   0x0000b318 +[CATransaction flush] + 32
23  UIKit                        0x00052e94 -[UIApplication _reportAppLaunchFinished] + 28
24  UIKit                        0x00004a80 -[UIApplication _runWithURL:sourceBundleID:] + 608
25  UIKit                        0x00055df8 -[UIApplication handleEvent:withNewEvent:] + 1516
26  UIKit                        0x00055634 -[UIApplication sendEvent:] + 60
27  UIKit                        0x0005508c _UIApplicationHandleEvent + 4528
28  GraphicsServices             0x00005988 PurpleEventCallback + 1044
29  CoreFoundation               0x00057524 CFRunLoopRunSpecific + 2296
30  CoreFoundation               0x00056c18 CFRunLoopRunInMode + 44
31  UIKit                        0x00003c00 -[UIApplication _run] + 512
32  UIKit                        0x00002228 UIApplicationMain + 960
33  BigNames                     0x0000293e main (main.m:14)
34  BigNames                     0x000028d4 start + 44
&lt;/pre&gt;

&lt;p&gt;Only the first few lines are relevant. The last bit of code that I control is loadView, and then it seems to get stuck in some sort of loop creating NSIndexPaths. &lt;/p&gt;

&lt;p&gt;I thought perhaps the custom table footer was causing problems (lines 5-7), but removing the footer had no effect.&lt;/p&gt;

&lt;p&gt;I was out of ideas, and the problem seemed to be in Apple&amp;rsquo;s UITableView code, so I decided I needed to send an email for the Apple developer support. If it was a bug that a UITableView couldn&amp;rsquo;t handle more than 4000 rows maybe they would have a workaround.&lt;/p&gt;

&lt;p&gt;I created a new project and made a UITableView with 10,000 rows. It loaded almost instantly. Weird. I then added some of the UITableViewDelegate and UITableViewDataSource methods and it became incredibly slow. The method that was causing the problems: &lt;tt&gt;tableView:heightForRowAtIndexPath:&lt;/tt&gt;&lt;/p&gt;

&lt;p&gt;I looked closer at the UITableViewDelegateProtocol documentation and found this blurb under the &lt;tt&gt;tableView:heightForRowAtIndexPath&lt;/tt&gt;:&lt;/p&gt;

&lt;blockquote style=&quot;text-align: left&quot;&gt;
There are performance implications to using tableView:heightForRowAtIndexPath: instead of rowHeight. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).
&lt;/blockquote&gt;

&lt;p&gt;Oh. (Why didn&amp;rsquo;t I see that before?) So, I removed this method from my UITableViewController subclass:&lt;/p&gt;

&lt;pre&gt;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	return ROW_HEIGHT;
}
&lt;/pre&gt;

&lt;p&gt;And added the following to the loadView method:&lt;/p&gt;

&lt;pre&gt;
self.tableView.rowHeight = ROW_HEIGHT;
&lt;/pre&gt;

&lt;p&gt;Done! And so ridiculously simple and obvious in hindsight. Is there a moral to this story? Well, a specific lesson is only use cells of a consistent height if your table might be long, and never use the inefficient &lt;tt&gt;heightForRowAtIndexPath:&lt;/tt&gt; if all cells are the same height. A more general take-away for me is that I should spend more time reading and re-reading the documentation. Not only is it easy to miss tiny details on an initial read, later versions of the documentation might specifically address the problem at hand. (I swear the issue on performance problems with &lt;tt&gt;heightForRowAtIndexPath:&lt;/tt&gt; wasn&amp;rsquo;t there when I first read the docs!)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>BikeFixTO: iPhone app source code for sale</title>
   <link href="http://xinsight.ca/blog/bikefixto-iphone-app-source-code-for-sale/"/>
   <updated>2009-07-09T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/bikefixto-iphone-app-source-code-for-sale</id>
   <content type="html">&lt;p&gt;&lt;img id=&quot;image56&quot; src=&quot;http://www.xinsight.ca/blog/wp-content/uploads/2009/07/bikefixto-merge-screenshots.jpg&quot; alt=&quot;bikefixto screenshots&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;a href=&quot;http://bikefixfinder.com/iphone/&quot;&gt;BikeFixTO&lt;/a&gt; - an iPhone app that shows your closest bike repair location. I&amp;rsquo;m pleased with how it turned out: The UI is nice and clean. The animated sorting of the nearest locations looks great, and I personally use the app all the time. So, on all these technical fronts, it was a hit.&lt;/p&gt;

&lt;p&gt;Where I&amp;rsquo;ve been less successful is on the marketing side. I never expected to sell thousands, but I expected to sell hundreds. And I had vague plans of rolling it out to other bike friendly cities. But, like so many developers I underestimated how much work it was involved in the sales process. It&amp;rsquo;s hard work to get the word out. Developers often have this idea that sales should be perfectly rational where the best software wins. The pitch is: &amp;ldquo;My software solves a problem you have. Therefore you should buy it.&amp;rdquo; And if there is no immediate response, they give up since clearly the person didn&amp;rsquo;t have that problem. The whole idea of &amp;ldquo;touching&amp;rdquo; people multiple times, reminding people, tweaking the message, running contests and having sales feels spammy and inefficient.&lt;/p&gt;

&lt;p&gt;And that is why developer&amp;rsquo;s need to consciously stop thinking like developers when it comes to marketing. And if that isn&amp;rsquo;t possible, they should partner with someone who can better wear the marketer&amp;rsquo;s hat.&lt;/p&gt;

&lt;p&gt;I would like &lt;strong&gt;BikeFixTO&lt;/strong&gt; to take on a new life. I would love it to spread to other cities where it can promote cycling, local bike shops and bicycle culture. Personally, I&amp;rsquo;m not the guy to do that, but &lt;strong&gt;maybe you are&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I&amp;rsquo;ve decided to make BikeFixTO free*, but am selling the source code. For $199 you get the full source to an iPhone application that you can build, tweak, customize and sell. There are no limitations with what you can do, except that I require that you don&amp;rsquo;t distribute the source code (which would prevent me from selling other copies).&lt;/p&gt;

&lt;p&gt;The code is also an excellent code base for anyone wanting to build location-aware applications. Some of the details about what you&amp;rsquo;ll find in the source code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;query data from sqlite database&lt;/li&gt;
&lt;li&gt;custom sqlite function to calculate distance between coordinates&lt;/li&gt;
&lt;li&gt;parse and display opening hours&lt;/li&gt;
	&lt;li&gt;animation to visually sort items&lt;/li&gt;
	&lt;li&gt;dial phone numbers&lt;/li&gt;
&lt;li&gt;php code for displaying maps on a webserver&lt;/li&gt;
&lt;li&gt;access GPS data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you&amp;rsquo;re interested, &lt;a href=&quot;http://bikefixfinder.com/iphone/&quot;&gt;download BikeFixTO&lt;/a&gt; and give it a test drive. &lt;a href=&quot;/contact/&quot;&gt;Drop me a line&lt;/a&gt; if you have any questions or would like to purchase the source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;*Update:&lt;/strong&gt; I&amp;rsquo;ve changed the price back to 99¢, but if you&amp;rsquo;re interested in kicking the tires, I&amp;rsquo;m happy to send you a promo code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;*Update 2:&lt;/strong&gt; I&amp;rsquo;ve updated the source to use MapKit.framework for fully dynamic map of all locations instead of a static map of a single location.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update (Oct 2011)&lt;/strong&gt; I&amp;rsquo;ve written a new location app from scratch. It has a web admin interface, automatically updates location changes and is searchable. For full details see the &lt;a href=&quot;/locations/&quot;&gt;Locations App page&lt;/a&gt;.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>How to setup a trivial forwarding mail server on a VPS</title>
   <link href="http://xinsight.ca/blog/how-to-setup-a-trivial-forwarding-mail-server-on-a-vps/"/>
   <updated>2008-11-05T00:00:00+01:00</updated>
   <id>http://xinsight.ca/blog/how-to-setup-a-trivial-forwarding-mail-server-on-a-vps</id>
   <content type="html">&lt;p&gt;Switching to a &lt;a href=&quot;https://manage.slicehost.com/customers/new?referrer=84a8a0f0df036ba85c989a3e71fa1f20&quot;&gt;virtual private server (VPS)&lt;/a&gt; from shared webhosting is a great way to improve performance, stability and security. For the most part, setting up the webserver and database is fairly routine and straightforward. However, setting up an email server is non-trivial.&lt;/p&gt;

&lt;p&gt;It&amp;rsquo;s a lot of work to run a full mailserver (and which is &lt;a href=&quot;http://tech.slashdot.org/article.pl?sid=08/05/27/137229&quot;&gt;why some webhosting companies recommend that you use a dedicated email service&lt;/a&gt;) but there are ways to setup a trivial mailserver that requires minimal maintenance. It simply accepts mail to various domains and then forwards them on to an actual mailserver.&lt;/p&gt;

&lt;p&gt;For this example, we&amp;rsquo;ll have two domains running on our VPS: xxx.com and yyy.com&lt;/p&gt;

&lt;p&gt;We&amp;rsquo;ll also have an &amp;ldquo;info&amp;rdquo; address at each domain that will be forwarded to  xxx@gmail.com and yyy@yahoo.com&lt;/p&gt;

&lt;p&gt;First, install a mailserver. I use the CentOS distribution which has the &lt;tt&gt;yum&lt;/tt&gt; package manager. It&amp;rsquo;s as easy as:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;yum install exim
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We need to create a file for each domain that we&amp;rsquo;ll be servicing.&lt;/p&gt;

&lt;p&gt;Create a directory to hold our virtual hosts:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mkdir /etc/exim/vhosts
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Create a configuration file for each domain. Use the same format as the /etc/aliases format.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;c&quot;&gt;# file:/etc/exim/vhosts/xxx.com&lt;/span&gt;
info: xxx@gmail.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;c&quot;&gt;# file:/etc/exim/vhosts/yyy.com&lt;/span&gt;
info: yyy@yahoo.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We need to tell exim to use these files when delivering mail. Near the top of /etc/exim.conf there will be a domainlist line.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;c&quot;&gt;# change...&lt;/span&gt;
domainlist &lt;span class=&quot;nv&quot;&gt;local_domains&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; @ : localhost : localhost.localdomain
&lt;span class=&quot;c&quot;&gt;# to...&lt;/span&gt;
domainlist &lt;span class=&quot;nv&quot;&gt;local_domains&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; @ : localhost : localhost.localdomain : dsearch;/etc/exim/vhosts
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Now exim will accept mail for our domains. To have it forward the mail correctly, add the following &amp;ldquo;router&amp;rdquo;. Look for &lt;tt&gt;begin routers&lt;/tt&gt;. The first router is probably &amp;ldquo;dnslookup&amp;rdquo;. After this, add the following:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;virtual:
  &lt;span class=&quot;nv&quot;&gt;driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; redirect
  &lt;span class=&quot;nv&quot;&gt;domains&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; dsearch;/etc/exim/vhosts
  &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$local_part&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;lsearch&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;/etc/exim/vhosts/&lt;span class=&quot;nv&quot;&gt;$domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;
  no_more
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Restart (or start) exim to have these changes take effect.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;/etc/init.d/exim restart
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;mx-records-for-your-domains&quot;&gt;MX Records for your domains&lt;/h3&gt;

&lt;p&gt;You have have a trivial mailserver. However, you need to specify that your server handles the mail for your domains. This is defined in the MX (Mail eXchange) record. To view the current setting:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dig -t mx xxx.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If the wrong address is returned (or no address) then you need to update your domain name servers. (Whoever provides you with domain name servers - typically your VPS provider or domain registrar - they should be able to set up MX records for you, or provide you with a tool so you can do this yourself.)&lt;/p&gt;

&lt;h3 id=&quot;testing-and-debugging&quot;&gt;Testing and Debugging&lt;/h3&gt;

&lt;p&gt;If you view the exim log file, you can watch mail being processed.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tail -f /var/log/exim/main.log
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If mail is not being routed directly, or you want to see more info, the debugging feature of exim is helpful. You can activate this by passing in &amp;lsquo;-v&amp;rsquo; flag when starting exim.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;exim -v -bd
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The process will not disconnect from the terminal, so you can see how exim processes mail as it arrives.&lt;/p&gt;

&lt;h3 id=&quot;starting-the-mailserver-automatically&quot;&gt;Starting the mailserver automatically&lt;/h3&gt;

&lt;p&gt;To make sure that exim is started as part of the boot process, you can mess with the symbolic links under /etc/rc.d or use this helper tool:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;chkconfig exim on
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;small&gt;
If you&amp;rsquo;re looking for a good VPS, I highly recommend &lt;a href=&quot;https://manage.slicehost.com/customers/new?referrer=84a8a0f0df036ba85c989a3e71fa1f20&quot;&gt;Slicehost&lt;/a&gt;. Their 256MB slice is $20USD/month is amazing. I love it.
&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: In mid-2010 I switched over to Linode. I love it even more. Much more bang for the buck.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Tips for configuring Apache's mod_rewrite</title>
   <link href="http://xinsight.ca/blog/tips-for-configuring-apaches-mod_rewrite/"/>
   <updated>2008-02-02T00:00:00+01:00</updated>
   <id>http://xinsight.ca/blog/tips-for-configuring-apaches-mod_rewrite</id>
   <content type="html">&lt;p&gt;Nice URLs are good interface design. But, it can be a struggle to get nice-looking urls with Apache.&lt;/p&gt;

&lt;p&gt;I just spent a frustrating hour fighting with Apache&amp;rsquo;s &lt;code&gt;mod_rewrite&lt;/code&gt;. There doesn&amp;rsquo;t seem to be a way to add commentary to the documentation, so I thought I would document a few tips here.&lt;/p&gt;

&lt;h3 id=&quot;tip-1-check-that-code-classinlinehtaccesscode-is-being-read-and-modrewrite-is-available&quot;&gt;TIP #1: Check that &lt;code class=&quot;inline&quot;&gt;.htaccess&lt;/code&gt; is being read and mod_rewrite is available&lt;/h3&gt;

&lt;p&gt;All of the apache configuration directives are contained in files named &lt;code&gt;.htaccess&lt;/code&gt;. To check that Apache has been configured to read these files, add the following to the &lt;code class=&quot;inline&quot;&gt;.htaccess&lt;/code&gt; in your webserver&amp;rsquo;s document root directory.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;nb&quot;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;On&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^foo$       rewriting-is-working
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Go to the url &lt;code class=&quot;inline&quot;&gt;http://site.com/foo&lt;/code&gt; and you should get a 404 error message like, &amp;ldquo;The requested URL /rewriting-is-working was not found on this server.&amp;rdquo; That&amp;rsquo;s good. mod_rewrite is installed and activated. It just needs to be configured.&lt;/p&gt;

&lt;p&gt;If you only see a &amp;ldquo;Not Found&amp;rdquo; error message with no mention of &amp;ldquo;rewriting-is-working&amp;rdquo;, then it might be that your &lt;code class=&quot;inline&quot;&gt;.htaccess&lt;/code&gt; file is not being read at all.&lt;/p&gt;

&lt;p&gt;An easy way to check if the &lt;code class=&quot;inline&quot;&gt;.htaccess&lt;/code&gt; files are being parsed is to put an invalid entry in the file at the root level. If you get a server error, then we know the file is in use.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;c&quot;&gt;# the next line is not valid. We should get a 500 Server Error.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;Foo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If you don&amp;rsquo;t get an error, then you need to allow the webserver to be configured with &lt;code class=&quot;inline&quot;&gt;.htaccess&lt;/code&gt; files. This is done in the main server config (usually &lt;code class=&quot;inline&quot;&gt;/etc/httpd.conf&lt;/code&gt; or &lt;code class=&quot;inline&quot;&gt;/etc/apache/httpd.conf&lt;/code&gt; ) and if you are on a shared webhost, you will have to ask the administrator to set this up. It will look something like&amp;hellip;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;c&quot;&gt;# file: /etc/httpd.conf&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;Directory&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;/public_html&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;AllowOverride&lt;/span&gt; FileInfo
&lt;span class=&quot;c&quot;&gt;# or if you want to do anything in .htaccess:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# AllowOverride All&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;(And of course, after any httpd.conf change, restart the webserver. &lt;code class=&quot;inline&quot;&gt;sudo apachectl restart&lt;/code&gt; )&lt;/p&gt;

&lt;h3 id=&quot;tip-2-redirects-must-have-different-names-than-files-and-directories&quot;&gt;TIP 2: Redirects must have different names than files and directories&lt;/h3&gt;

&lt;p&gt;This was a huge stumbling block for me. The filesystem takes precedence over the URL rewriting. Consider this example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  FILE:
  public_html/about/us.html

  URL:
  http://site.com/about/us.html

  DESIRED URL:
  http://site.com/about

  HTACCESS:
  public_html/.htaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is &lt;strong&gt;not possible&lt;/strong&gt;.  Apache will go into the &amp;ldquo;about&amp;rdquo; directory and will never see the Rewrite directive at the root level.&lt;/p&gt;

&lt;p&gt;The best solution is to rename &amp;ldquo;about&amp;rdquo; something like &amp;ldquo;_about&amp;rdquo; and then add the following rewrite rules:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;nb&quot;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;On&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^about$       /_about/us.html
&lt;span class=&quot;c&quot;&gt;# - and if you need the old URL to work...&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# RewriteRule ^about/us.html$ /about/us.html&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# - or if there are other files under /about ...  &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# RewriteRule ^about/(.+)$ /_about/$1 &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;tip-3-the-url-your-pattern-sees-is-relative-to-the-current-directory&quot;&gt;TIP 3: The URL your pattern sees is relative to the current directory&lt;/h3&gt;

&lt;p&gt;If your &lt;code class=&quot;inline&quot;&gt;.htaccess&lt;/code&gt; is in a subdirectory, the Rewrite rule cannot see the entire URL. Consider this example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  FILE:
  public_html/content/news.php
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;URL:
http://site.com/content/news.php&lt;/p&gt;

  &lt;p&gt;DESIRED URL:
http://site.com/content/latest&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTACCESS:
public_html/content/.htaccess&lt;/p&gt;

&lt;p&gt;The rewrite rule in the &lt;code&gt;.htaccess&lt;/code&gt; file doesn&amp;rsquo;t &amp;ldquo;see&amp;rdquo; the &amp;ldquo;content&amp;rdquo; part of the URL. To get the desired URL, the pattern is &lt;code class=&quot;inline&quot;&gt;^latest$&lt;/code&gt; (&lt;b&gt;Not&lt;/b&gt;: &lt;code class=&quot;inline&quot;&gt;^content/latest$&lt;/code&gt;!)&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;nb&quot;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;On&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^latest$					news.php
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;tip-4-use-minimal-options&quot;&gt;TIP 4: use minimal options&lt;/h3&gt;

&lt;p&gt;When nothing is working, it&amp;rsquo;s tempting to just start sticking various options to RewriteRule to see what happens.&lt;/p&gt;

&lt;p&gt;The options that you can pass to RewriteRule are a bit cyptic. They are:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[L] = Last rule. If there is a match then stop processing further Rewrite rules.
[QSA] = Query String Append. Useful for scripts that receive parameters via a GET (foo.php?id=123&amp;amp;name=joe) and you simply want to pass them on.
[PT] = Pass through. Mostly used in combination wiAfter a match, pass through to the other handlers. U
[R] = Redirect. Send the rewritten URL back to the browser, which the browser automatically loads. The new URL is visible to end-user.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The [L]ast rule is a nice optimization if you have several rules, since the server won&amp;rsquo;t continue to match rules that you don&amp;rsquo;t need or want. Having rules that call other rules seems like a recipe for spaghetti code. For example, this is possible:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;nb&quot;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;On&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^xxx$       yyy
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^yyy$       zzz
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^zzz$       news.php
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&amp;ldquo;xxx&amp;rdquo;, &amp;ldquo;yyy&amp;rdquo; or &amp;ldquo;zzz&amp;rdquo; would all take you to &amp;ldquo;news.php&amp;rdquo;. Useful? Maybe. It&amp;rsquo;s certainly convoluted.&lt;/p&gt;

&lt;p&gt;Here&amp;rsquo;s how you add an options to your rule.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;nb&quot;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;On&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^latest$					news.php [L]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;[QSA] is only useful if you have a script that receives input multiple variables via a GET request. Assuming that news.php takes several parameters&amp;hellip;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;nb&quot;&gt;RewriteEngine&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;On&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^latest$					news.php [QSA,L]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;[PT] is only useful if you want mod_alias to process the URL afterwards. (But why not just write the URL correctly the first time?)&lt;/p&gt;

&lt;h3 id=&quot;tip-5-use-simple-patterns&quot;&gt;TIP 5: Use simple patterns&lt;/h3&gt;

&lt;p&gt;The regexp operator &lt;code class=&quot;inline&quot;&gt;\d&lt;/code&gt; which normally matches all digits, doesn&amp;rsquo;t work! I figured this was a fairly basic to any regular expression engine, but it must be a perl extension. Anyways, keep it simple. If your regexp doesn&amp;rsquo;t work, simplify it to the basics. &lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  URL:
  http://site.com/_item.php?id=123

  DESIRED URL:
  http://site.com/item/123
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;apache&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Bad: Does NOT work!&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^item/(\d+)       _item.php?id=$1

&lt;span class=&quot;c&quot;&gt;# Good&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;RewriteRule&lt;/span&gt; ^item/([0-9]+)    _item.php?id=$1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Thematic maps: Adjusting the number of gradients and colors for maximum usability</title>
   <link href="http://xinsight.ca/blog/thematic-maps-adjusting-the-number-of-gradients-and-colors-for-maximum-usability/"/>
   <updated>2007-10-11T00:00:00+02:00</updated>
   <id>http://xinsight.ca/blog/thematic-maps-adjusting-the-number-of-gradients-and-colors-for-maximum-usability</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;A few years ago I did some usability consulting for a mapping service called PushPin (since acquired by Apple). This post was originally published on their developer blog - hence the American spelling of &amp;lsquo;color&amp;rsquo;. -jason&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The default number of breaks for a gradient in the Pushpin API is eight. This relatively fine level of breaks can be useful when you need to know exact values for particular regions, but it can also prevent getting a broad overview of an area. For example, if you wanted to look at a city, and identify where there was the greatest increase in subprime mortgages, having fewer gradients (or fewer breaks) would show the &amp;lsquo;hotspots&amp;rsquo; more clearly.&lt;/p&gt;

&lt;p&gt;In this example, we can see how reducing the number of breaks from 8 to 4, cleans up the map. For example, some of the medium-shaded purple values around Monterey Park are pushed to lighter and darker colors. Reducing the number of breaks is much like pumping up the contrast on a photo.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;image37&quot; src=&quot;http://xinsight.ca/blog/wp-content/uploads/2007/10/breaks-8-vs-4.gif&quot; alt=&quot;Number of Breaks: 8 vs 4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Reducing the number of breaks can be done with the Pushpin API as follows:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// create a map&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;map&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// for a better overview,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// reduce from the number of breaks from 8 to 4&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getLegend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setNumberOfBreaks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Another interesting finding from a recent round of usability tests, was that the participants had a hard time distinguishing between the darker colors of the gradient we were using. They could identify colors as being different, but they were not able to easily locate the color in the legend. They would look at the map and then move to the legend, and then go back to the map several times before hesitantly venturing a guess as to what a particular shade of purple meant.&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;image38&quot; src=&quot;http://xinsight.ca/blog/wp-content/uploads/2007/10/legend-purple.gif&quot; alt=&quot;legend-purple.gif&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Partly this was due to the large number of breaks we were using (8), but it was also due to our color choices. A lot of people have spent a lot of time thinking about maps and coloring over the years, so when selecting colors, it&amp;rsquo;s probably best to put away Photoshop and try to leverage conventions and the experience of others. An amazing tool like &lt;a href=&quot;http://www.colorbrewer.org&quot;&gt;ColorBrewer&lt;/a&gt; will allow you to browse various gradients and find one that suits your application.&lt;/p&gt;

&lt;p&gt;One final note on color gradients: Don&amp;rsquo;t forget about negative values. In a development system we were testing, we used a single gradient for all the datasets that could be mapped. This was good in that it was consistent and allowed participants to know that the dark purple indicated areas of high intensity, and light pink indicated low intensity. Where it became a problem was when the indicator used negative values, such as &lt;b&gt;percent change&lt;/b&gt;. Dark purple still meant intensity, but light pink no longer meant low intensity, but &lt;b&gt;negative&lt;/b&gt; intensity. Test participants interpreted the light pink as being a &amp;lsquo;low&amp;rsquo; value, when in fact it was actually a high value (albeit a negative one).&lt;/p&gt;

&lt;p&gt;&lt;img id=&quot;image39&quot; src=&quot;http://xinsight.ca/blog/wp-content/uploads/2007/10/compare-neg.gif&quot; alt=&quot;negative values require a different gradient&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From these images, it&amp;rsquo;s clear that when negative values are mapped to a separate color, the map changes drastically, and eliminates the chance of confusing a low value with a high negative value.&lt;/p&gt;

&lt;p&gt;You can define custom gradient colors in the Pushpin API as follows:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;map&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// center on LA&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;map2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setCenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PLatLng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;34.045&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;118.235&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Create custom color ramp&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mycolorramp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColorRamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;mine&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;467D73&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;78A892&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;CCE3CF&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;FEE6F4&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;E5CCE1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;CBB1CC&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;9B839B&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;5D4670&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Set map&amp;#39;s legend to new color ramp&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getLegend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setColorRamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mycolorramp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// add a particular indicator&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setIndicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;168524&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;references&quot;&gt;References:&lt;/h3&gt;

&lt;p&gt;[1] Color Brewer &lt;a href=&quot;http://www.colorbrewer.org/&quot;&gt;http://www.colorbrewer.org/&lt;/a&gt;&lt;/p&gt;

&lt;div style=&quot;border: #999 solid 1px; padding: 6px; color: #fff; background: #766;&quot;&gt;
Jason Moore is an interaction researcher and designer, based in Toronto, Canada. Jason has performed a series of interviews and usability studies with Pushpin users, and will be sharing some of the findings on the Pushpin Developer blog.
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Painless Selling on Craigslist</title>
   <link href="http://xinsight.ca/blog/my-system-for-selling-stuff-on-craigslist/"/>
   <updated>2007-01-25T00:00:00+01:00</updated>
   <id>http://xinsight.ca/blog/my-system-for-selling-stuff-on-craigslist</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;This is an old article that isn&amp;rsquo;t related to the rest of my postings on this site, but it still seems to get a lot of search traffic. And since craigslist hasn&amp;rsquo;t changed much in the last 10 years, it&amp;rsquo;s still relevant. -jason&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I love craigslist. It&amp;rsquo;s simply the best way to get rid of things you no longer need, reduce what goes to landfill and make a few bucks. (It beats ebay, since it&amp;rsquo;s free and you don&amp;rsquo;t have to pack items of schlep them to the post office.) I moved recently, and while re-evaluating my worldly possessions, I sold lot of things on craigslist. There is a bit of a learning curve, so here is the system I eventually settled on.&lt;/p&gt;

&lt;h2 id=&quot;create-an-account&quot;&gt;Create an account&lt;/h2&gt;

&lt;p&gt;It only takes a minute and then you can manage all your items on your my account page. (If you don&amp;rsquo;t, you have to painfully dig around in your email for the authorization links.)&lt;/p&gt;

&lt;h2 id=&quot;take-photos&quot;&gt;Take photos&lt;/h2&gt;

&lt;p&gt;There&amp;rsquo;s no excuse for not having photos in your ad. Buy one, borrow one, use your webcam, whatever. Not only is this nice for the buyers, but if people know exactly what you&amp;rsquo;re selling than you won&amp;rsquo;t have to describe it to them in email or on the phone and they are less likely to show up and decide that they don&amp;rsquo;t want it. Yes, it&amp;rsquo;s a bit more work to snap a picture and get it onto your computer, but it&amp;rsquo;ll save everyone time in the end.&lt;/p&gt;

&lt;h2 id=&quot;control-your-contact-information&quot;&gt;Control your contact information&lt;/h2&gt;

&lt;p&gt;You want to have some control over who has your email and phone number, so don&amp;rsquo;t post it in directly in the advertisement. Just focus on describing the item. (I initially posted full info and then people contacting me long after it was sold.) Use the anonymous email option that craigslist provides. It will save you from spam and phone calls.&lt;/p&gt;

&lt;h2 id=&quot;provide-nearest-intersection&quot;&gt;Provide nearest intersection&lt;/h2&gt;

&lt;p&gt;You have an option of stating where you are located in your posting. Some people put something generic (like &amp;lsquo;Toronto&amp;rsquo; or &amp;lsquo;GTA&amp;rsquo;) which is useless. Put the nearest major intersection so readers can figure out if it is a 5-minute walk or a 1-hour drive.&lt;/p&gt;

&lt;h2 id=&quot;have-a-boilerplate-response&quot;&gt;Have a boilerplate response&lt;/h2&gt;

&lt;p&gt;When someone inquires about an item, I answer any questions and then send them a standard response. This includes my address, basic driving/transit directions, phone number, and my terms. Saves a lot of time. My terms are: &lt;strong&gt;All sales final&lt;/strong&gt;, &lt;strong&gt;Cash and carry&lt;/strong&gt; and &lt;strong&gt;First come first serve&lt;/strong&gt;. (more about the last one below.)&lt;/p&gt;

&lt;h2 id=&quot;dealing-with-no-shows&quot;&gt;Dealing with No-shows&lt;/h2&gt;

&lt;p&gt;For some reason, craigslist buyers love to say they will come by and then don&amp;rsquo;t. So, my solution is &lt;strong&gt;first come, first serve&lt;/strong&gt;. Put this wonderful phrase in your standard email that you send out to interested buyers. I forgot once, and this woman who wanted to come in five days was upset when I emailed her to tell her that I had just sold it. &amp;ldquo;You mean you sold it after we scheduled an appointment??&amp;rdquo; Did she expect me to say no to someone at my door with money, because she was maybe coming next week? People are crazy.&lt;/p&gt;

&lt;h2 id=&quot;email-the-non-buyers-when-something-sells&quot;&gt;Email the non-buyers when something sells&lt;/h2&gt;

&lt;p&gt;If ten people email me about an item, and I&amp;rsquo;ve sent all of them my address, I don&amp;rsquo;t want them showing up after it&amp;rsquo;s gone. It&amp;rsquo;s a bit of extra work, but I send each of them an email saying:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Sorry, but xyz has been sold. Thanks for your interest!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;free-items-are-a-hassle&quot;&gt;Free items are a hassle&lt;/h2&gt;

&lt;p&gt;Nothing brings out the eccentrics like giving away something for free. And just because it&amp;rsquo;s free, don&amp;rsquo;t think that people will be any easier to deal with. First, if it isn&amp;rsquo;t total garbage, you&amp;rsquo;ll get a flood of emails. Then, you&amp;rsquo;ll get calls. Then you&amp;rsquo;ll have to setup a time for them to come and pick it up. Then you&amp;rsquo;ll have no-shows. In other words, it&amp;rsquo;s exactly the same amount of work as selling something, except you don&amp;rsquo;t get any money for your trouble.&lt;/p&gt;

&lt;p&gt;I suggest a minimum price of $5, which is sort of a bozo filter. If you can&amp;rsquo;t sell it for $5, then just haul it out the curb and post on craigslist&amp;rsquo;s &amp;ldquo;free stuff&amp;rdquo; area with the address and no contact info, and your job is done.&lt;/p&gt;

&lt;h2 id=&quot;remove-old-ads&quot;&gt;Remove old ads&lt;/h2&gt;

&lt;p&gt;Great, you sold that old record player to some chump for $10. Now, go to your &amp;ldquo;my account&amp;rdquo; page, and delete the ad. (Some people like to edit their posting, and add &amp;ldquo;SOLD&amp;rdquo; or &amp;ldquo;GONE&amp;rdquo; to the subject. Unless your posting is so good it will make it to &lt;a href=&quot;http://www.craigslist.org/about/best/all/&quot;&gt;best of craigslist&lt;/a&gt;, it&amp;rsquo;s just noise. Just delete it.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;• • •&lt;/p&gt;

&lt;p&gt;So, those are my craigslist tips, now start selling some junk!&lt;/p&gt;
</content>
 </entry>
 

</feed>

