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

<channel>
	<title>Backspace Prologue &#187; Troy</title>
	<atom:link href="http://troybrant.net/blog/author/admin/feed/" rel="self" type="application/rss+xml" />
	<link>http://troybrant.net/blog</link>
	<description>Mistakes and learnings of an iPhone developer</description>
	<lastBuildDate>Fri, 08 Jul 2011 22:15:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Simple XML to NSDictionary Converter</title>
		<link>http://troybrant.net/blog/2010/09/simple-xml-to-nsdictionary-converter/</link>
		<comments>http://troybrant.net/blog/2010/09/simple-xml-to-nsdictionary-converter/#comments</comments>
		<pubDate>Sun, 19 Sep 2010 05:26:04 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=68</guid>
		<description><![CDATA[For the past year or so, I&#8217;ve been very lucky. All the data I&#8217;ve had to deal with has been packaged in JSON, not XML. And what a glorious year it&#8217;s been. Instead of writing complex, single-use XML-parsing code, I&#8217;ve had the joy of using Stig Brautaset&#8217;s excellent JSON-framework to parse JSON. The framework is [...]]]></description>
			<content:encoded><![CDATA[<p>For the past year or so, I&#8217;ve been very lucky. All the data I&#8217;ve had to deal with has been packaged in JSON, not XML. And what a glorious year it&#8217;s been. Instead of writing complex, single-use XML-parsing code, I&#8217;ve had the joy of using Stig Brautaset&#8217;s excellent <a href="http://github.com/stig/json-framework">JSON-framework</a> to parse JSON. The framework is dead simple to use. Have some JSON? Throw some JSON at the framework, and <em>viola!</em> You get back an <code>NSDictionary</code> or an <code>NSArray</code>. Just one line of code, and you&#8217;re done. Simple, elegant, and completely opposite to the experience of parsing XML.</p>
<p>What&#8217;s the problem parsing XML? Well, first you have to set up your <code>NSXMLParser</code>. Then, make sure you&#8217;re set as the delegate. Then, override the necessary delegate methods (there are <a href="http://developer.apple.com/library/ios/#documentation/cocoa/reference/NSXMLParserDelegate_Protocol/Reference/Reference.html">14 relevant NSXMLParserDelegate methods</a>, so choose wisely!). Then, initialize your <code>NSMutableString</code> to record the strings from the text nodes. Then, initialize your model objects from the XML as elements are pushed and popped in the didStart and didEnd methods. And don&#8217;t forget to update your objects with data in the attributes dictionary. And so on, and so on.</p>
<p>XML files, especially small XML files, should be as easy to parse as JSON.</p>
<p>Last week, when faced with the depressing task of writing my first XML parser this year, I wrote a general-purpose XML to <code>NSDictionary</code> parser instead. Throw some XML at it, and it spits out an <code>NSDictionary</code>.</p>
<p>How does it work? Here are the key ideas:</p>
<ol>
<li>XML elements map to key names in the dictionary</li>
<li>Each element corresponds to a child dictionary</li>
<li>Attribute key-value pairs are added to the element&#8217;s child dictionary</li>
<li>Strings from text nodes are assigned to the child dictionary&#8217;s &#8220;text&#8221; key</li>
<li>If an element name is encountered multiple times, the value of the element is set to an array of children dictionaries</li>
</ol>
<p>This conversion is not without its flaws, but it should work pretty well for most XML files.</p>
<h2>The Code</h2>
<p>The parser consists of a single class, <code>XMLReader</code>. You can either pass it an XML string or an XML data object, and it will return the <code>NSDictionary</code> version of the XML. If the XML is malformed or the parser fails for any other reason, the <code>NSError</code> pointer you pass in will be populated with an <code>NSError</code> object.</p>
<p>Here&#8217;s the header file:</p>
<div class="code">
<span class="comment">//</span><br />
<span class="comment">//  XMLReader.h</span><br />
<span class="comment">//</span> </p>
<p><span class="preprocessor">#import <span class="string">&lt;Foundation/Foundation.h&gt;</span></span> </p>
<p><span class="directive">@interface</span> XMLReader : NSObject<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSMutableArray</span> *<span class="ivar">dictionaryStack</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSMutableString</span> *<span class="ivar">textInProgress</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSError</span> **<span class="ivar">errorPointer</span>;<br />
} </p>
<p>+ (<span class="built_in_type">NSDictionary</span> *)dictionaryForXMLData:(<span class="built_in_type">NSData</span> *)data error:(<span class="built_in_type">NSError</span> **)errorPointer;<br />
+ (<span class="built_in_type">NSDictionary</span> *)dictionaryForXMLString:(<span class="built_in_type">NSString</span> *)string error:(<span class="built_in_type">NSError</span> **)errorPointer;</p>
<p><span class="directive">@end</span> </p>
</div>
<p>And the implementation:</p>
<div class="code">
<span class="comment">//</span><br />
<span class="comment">//  XMLReader.m</span><br />
<span class="comment">//</span> </p>
<p><span class="preprocessor">#import <span class="string">&quot;XMLReader.h&quot;</span></span> </p>
<p><span class="built_in_type">NSString</span> *<span class="var">const</span> <span class="var">kXMLReaderTextNodeKey</span> = <span class="string">@&quot;text&quot;</span>;</p>
<p><span class="directive">@interface</span> XMLReader (Internal) </p>
<p>&#45; (<span class="keyword">id</span>)initWithError:(<span class="built_in_type">NSError</span> **)error;<br />
&#45; (<span class="built_in_type">NSDictionary</span> *)objectWithData:(<span class="built_in_type">NSData</span> *)data;</p>
<p><span class="directive">@end</span> </p>
<p><span class="directive">@implementation</span> XMLReader </p>
<p><span class="preprocessor">#pragma mark &#45;</span><br />
<span class="preprocessor">#pragma mark Public methods</span> </p>
<p>+ (<span class="built_in_type">NSDictionary</span> *)dictionaryForXMLData:(<span class="built_in_type">NSData</span> *)data error:(<span class="built_in_type">NSError</span> **)error<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="var">XMLReader</span> *<span class="var">reader</span> = [[<span class="var">XMLReader</span> <span class="method_call">alloc</span>] <span class="method_call">initWithError</span>:<span class="var">error</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSDictionary</span> *<span class="var">rootDictionary</span> = [<span class="var">reader</span> <span class="method_call">objectWithData</span>:<span class="var">data</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">reader</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="var">rootDictionary</span>;<br />
} </p>
<p>+ (<span class="built_in_type">NSDictionary</span> *)dictionaryForXMLString:(<span class="built_in_type">NSString</span> *)string error:(<span class="built_in_type">NSError</span> **)error<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSData</span> *<span class="var">data</span> = [<span class="var">string</span> <span class="method_call">dataUsingEncoding</span>:<span class="built_in_type">NSUTF8StringEncoding</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> [<span class="var">XMLReader</span> <span class="method_call">dictionaryForXMLData</span>:<span class="var">data</span> <span class="method_call">error</span>:<span class="var">error</span>];<br />
} </p>
<p><span class="preprocessor">#pragma mark &#45;</span><br />
<span class="preprocessor">#pragma mark Parsing</span> </p>
<p>&#45; (<span class="keyword">id</span>)initWithError:(<span class="built_in_type">NSError</span> **)error<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="keyword">self</span> = [<span class="keyword">super</span> <span class="method_call">init</span>])<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">errorPointer</span> = <span class="var">error</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">self</span>;<br />
} </p>
<p>&#45; (<span class="primitive">void</span>)dealloc<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">dictionaryStack</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">textInProgress</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">super</span> <span class="method_call">dealloc</span>];<br />
} </p>
<p>&#45; (<span class="built_in_type">NSDictionary</span> *)objectWithData:(<span class="built_in_type">NSData</span> *)data<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Clear out any old data</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">dictionaryStack</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">textInProgress</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">dictionaryStack</span> = [[<span class="built_in_type">NSMutableArray</span> <span class="method_call">alloc</span>] <span class="method_call">init</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">textInProgress</span> = [[<span class="built_in_type">NSMutableString</span> <span class="method_call">alloc</span>] <span class="method_call">init</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Initialize the stack with a fresh dictionary</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">dictionaryStack</span> <span class="method_call">addObject</span>:[<span class="built_in_type">NSMutableDictionary</span> <span class="method_call">dictionary</span>]];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Parse the XML</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSXMLParser</span> *<span class="var">parser</span> = [[<span class="built_in_type">NSXMLParser</span> <span class="method_call">alloc</span>] <span class="method_call">initWithData</span>:<span class="var">data</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="var">parser</span>.<span class="property_call">delegate</span> = <span class="keyword">self</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">BOOL</span> <span class="var">success</span> = [<span class="var">parser</span> <span class="method_call">parse</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Return the stack&#8217;s root dictionary on success</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">success</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSDictionary</span> *<span class="var">resultDict</span> = [<span class="ivar">dictionaryStack</span> <span class="method_call">objectAtIndex:</span><span class="number">0</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="var">resultDict</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">nil</span>;<br />
} </p>
<p><span class="preprocessor">#pragma mark &#45;</span><br />
<span class="preprocessor">#pragma mark NSXMLParserDelegate methods</span> </p>
<p>&#45; (<span class="primitive">void</span>)parser:(<span class="built_in_type">NSXMLParser</span> *)parser didStartElement:(<span class="built_in_type">NSString</span> *)elementName namespaceURI:(<span class="built_in_type">NSString</span> *)namespaceURI qualifiedName:(<span class="built_in_type">NSString</span> *)qName attributes:(<span class="built_in_type">NSDictionary</span> *)attributeDict<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Get the dictionary for the current level in the stack</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSMutableDictionary</span> *<span class="var">parentDict</span> = [<span class="ivar">dictionaryStack</span> <span class="method_call">lastObject</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Create the child dictionary for the new element, and initilaize it with the attributes</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSMutableDictionary</span> *<span class="var">childDict</span> = [<span class="built_in_type">NSMutableDictionary</span> <span class="method_call">dictionary</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">childDict</span> <span class="method_call">addEntriesFromDictionary</span>:<span class="var">attributeDict</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// If there&#8217;s already an item for this key, it means we need to create an array</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">id</span> <span class="var">existingValue</span> = [<span class="var">parentDict</span> <span class="method_call">objectForKey</span>:<span class="var">elementName</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">existingValue</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSMutableArray</span> *<span class="var">array</span> = <span class="keyword">nil</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> ([<span class="var">existingValue</span> <span class="method_call">isKindOfClass</span>:[<span class="built_in_type">NSMutableArray</span> <span class="method_call">class</span>]])<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// The array exists, so use it</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="var">array</span> = (<span class="built_in_type">NSMutableArray</span> *) <span class="var">existingValue</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Create an array if it doesn&#8217;t exist</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="var">array</span> = [<span class="built_in_type">NSMutableArray</span> <span class="method_call">array</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">array</span> <span class="method_call">addObject</span>:<span class="var">existingValue</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Replace the child dictionary with an array of children dictionaries</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">parentDict</span> <span class="method_call">setObject</span>:<span class="var">array</span> <span class="method_call">forKey</span>:<span class="var">elementName</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Add the new child dictionary to the array</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">array</span> <span class="method_call">addObject</span>:<span class="var">childDict</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// No existing value, so update the dictionary</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">parentDict</span> <span class="method_call">setObject</span>:<span class="var">childDict</span> <span class="method_call">forKey</span>:<span class="var">elementName</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Update the stack</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">dictionaryStack</span> <span class="method_call">addObject</span>:<span class="var">childDict</span>];<br />
} </p>
<p>&#45; (<span class="primitive">void</span>)parser:(<span class="built_in_type">NSXMLParser</span> *)parser didEndElement:(<span class="built_in_type">NSString</span> *)elementName namespaceURI:(<span class="built_in_type">NSString</span> *)namespaceURI qualifiedName:(<span class="built_in_type">NSString</span> *)qName<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Update the parent dict with text info</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSMutableDictionary</span> *<span class="var">dictInProgress</span> = [<span class="ivar">dictionaryStack</span> <span class="method_call">lastObject</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Set the text property</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> ([<span class="ivar">textInProgress</span> <span class="method_call">length</span>] <span class="var">&gt;</span> <span class="number">0</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Get rid of leading + trailing whitespace</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">dictInProgress</span> <span class="method_call">setObject</span>:<span class="var">textInProgress</span> <span class="method_call">forKey</span>:<span class="var">kXMLReaderTextNodeKey</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Reset the text</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">textInProgress</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">textInProgress</span> = [[<span class="built_in_type">NSMutableString</span> <span class="method_call">alloc</span>] <span class="method_call">init</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Pop the current dict</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">dictionaryStack</span> <span class="method_call">removeLastObject</span>];<br />
} </p>
<p>&#45; (<span class="primitive">void</span>)parser:(<span class="built_in_type">NSXMLParser</span> *)parser foundCharacters:(<span class="built_in_type">NSString</span> *)string<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Build the text value</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">textInProgress</span> <span class="method_call">appendString</span>:<span class="var">string</span>];<br />
} </p>
<p>&#45; (<span class="primitive">void</span>)parser:(<span class="built_in_type">NSXMLParser</span> *)parser parseErrorOccurred:(<span class="built_in_type">NSError</span> *)parseError<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Set the error pointer to the parser&#8217;s error object</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;*<span class="ivar">errorPointer</span> = <span class="var">parseError</span>;<br />
} </p>
<p><span class="directive">@end</span> </p>
</div>
<p>The code works by keeping a stack of dictionaries, one for each level of the XML file. Each time a new tag is encountered, a child dictionary is pushed onto the stack. Each time a tag is closed, the dictionary is popped off the stack.</p>
<p>Arrays of elements are detected when the same key appears twice in the dictionary. For instance, if the XML is &#8220;&lt;book&gt;&lt;page&gt;1&lt;/page&gt;&lt;page&gt;2&lt;/page&gt;&lt;/book&gt;&#8221;, the first time the &#8220;page&#8221; element is encountered, a child dictionary will be set as the value for the &#8220;page&#8221; key. The next time the &#8220;page&#8221; element is encountered, we detect that there&#8217;s already a value for the &#8220;page&#8221; key, so we put both pages in an array and set the value of the &#8220;page&#8221; key to the array.</p>
<p><em>Note: One side effect of detecting arrays in this fashion is that the value for a key (say &#8220;page&#8221; in the example above), can sometimes be set to an NSArray but other times be set to an NSDictionary. For example, if the book contains a single page, &#8220;page&#8221; will be set to an NSDictionary. If the book contains 2 or more pages, &#8220;page&#8221; will be set to an NSArray. You will need to account for this when reading from the dictionary produced by dictionaryFromXMLString:error: and dictionaryFromXMLData:error:.</em></p>
<p>When the parser comes across a text node in the XML, it inserts a key into the dictionary named &#8220;text&#8221; and sets the value to the parsed string.</p>
<p><em>Note: Make sure the XML you&#8217;re parsing doesn&#8217;t contain a field named &#8220;text&#8221;! You can change it to a non-conflicting name by editing the kXMLReaderTextNodeKey constant at the top of XMLReader.m.</em></p>
<h2>Using the Code</h2>
<p>The conversion is best illustrated by an example. The snippet below defines an XML string that is converted into a dictionary using <code>XMLReader</code>:</p>
<div class="code">
<span class="comment">//</span><br />
<span class="comment">// XML string from <a href="http://labs.adobe.com/technologies/spry/samples/data_region/NestedXMLDataSample.html">http://labs.adobe.com/technologies/spry/samples/data_region/NestedXMLDataSample.html</a></span><br />
<span class="comment">//</span><br />
<span class="built_in_type">NSString</span> *<span class="var">testXMLString</span> = <span class="string">@&quot;&lt;items&gt;&lt;item id=\&quot;0001\&quot; type=\&quot;donut\&quot;&gt;&lt;name&gt;Cake&lt;/name&gt;&lt;ppu&gt;0.55&lt;/ppu&gt;&lt;batters&gt;&lt;batter id=\&quot;1001\&quot;&gt;Regular&lt;/batter&gt;&lt;batter id=\&quot;1002\&quot;&gt;Chocolate&lt;/batter&gt;&lt;batter id=\&quot;1003\&quot;&gt;Blueberry&lt;/batter&gt;&lt;/batters&gt;&lt;topping id=\&quot;5001\&quot;&gt;None&lt;/topping&gt;&lt;topping id=\&quot;5002\&quot;&gt;Glazed&lt;/topping&gt;&lt;topping id=\&quot;5005\&quot;&gt;Sugar&lt;/topping&gt;&lt;/item&gt;&lt;/items&gt;&quot;</span>;</p>
<p><span class="comment">// Parse the XML into a dictionary</span><br />
<span class="built_in_type">NSError</span> *<span class="var">parseError</span> = <span class="keyword">nil</span>;<br />
<span class="built_in_type">NSDictionary</span> *<span class="var">xmlDictionary</span> = [<span class="var">XMLReader</span> <span class="method_call">dictionaryForXMLString</span>:<span class="var">testXMLString</span> <span class="method_call">error</span>:&amp;<span class="var">parseError</span>];</p>
<p><span class="comment">// Print the dictionary</span><br />
<span class="built_in_type">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="var">xmlDictionary</span>);</p>
<p><span class="comment">//</span><br />
<span class="comment">// testXMLString = </span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&lt;items&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;item id=&#8221;0001&#8243; type=&#8221;donut&#8221;&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;Cake&lt;/name&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;ppu&gt;0.55&lt;/ppu&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;batters&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;batter id=&#8221;1001&#8243;&gt;Regular&lt;/batter&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;batter id=&#8221;1002&#8243;&gt;Chocolate&lt;/batter&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;batter id=&#8221;1003&#8243;&gt;Blueberry&lt;/batter&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/batters&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;topping id=&#8221;5001&#8243;&gt;None&lt;/topping&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;topping id=&#8221;5002&#8243;&gt;Glazed&lt;/topping&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;topping id=&#8221;5005&#8243;&gt;Sugar&lt;/topping&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/item&gt;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&lt;/items&gt;</span><br />
<span class="comment">//</span><br />
<span class="comment">// is converted into</span><br />
<span class="comment">//</span><br />
<span class="comment">// xmlDictionary = {</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;items = {</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;item =  {</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 0001;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type = donut;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name = {</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = Cake;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ppu = {</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = 0.55;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;batters = {</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;batter =  (</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 1001;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = Regular;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 1002;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = Chocolate;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 1003;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = Blueberry;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;topping = (</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 5001;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = None;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 5002;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = Glazed;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id = 5005;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = Sugar;</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);</span><br />
<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};</span><br />
<span class="comment">// &nbsp;&nbsp;&nbsp;&nbsp;};</span><br />
<span class="comment">// }</span><br />
<span class="comment">//</span>
</div>
<p>The mapping between XML and NSDictionary is shown in the comments above. Notice how the &#8220;batter&#8221; and &#8220;topping&#8221; keys are set to arrays since there were multiple &#8220;batter&#8221; and &#8220;topping&#8221; keys in the XML. Also, note how the attributes for elements are available in the element&#8217;s child dictionary. For instance, the &#8220;id&#8221; and &#8220;type&#8221; attributes for item &#8220;item&#8221; are keys in the &#8220;item&#8221; dictionary.</p>
<h2>Download</h2>
<p>You can download the XMLReader files here:</p>
<p><a href="http://troybrant.net/blog/downloads/XMLReader.zip">XMLReader.zip</a></p>
<h2>Done and Done</h2>
<p><code>XMLReader</code> isn&#8217;t perfect by any stretch of the imagination, but hopefully it helps save you some pain and misery next time you need to parse an XML file in Objective-C.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/09/simple-xml-to-nsdictionary-converter/feed/</wfw:commentRss>
		<slash:comments>33</slash:comments>
		</item>
		<item>
		<title>Adding firstObject to NSArray</title>
		<link>http://troybrant.net/blog/2010/02/adding-firstobject-to-nsarray/</link>
		<comments>http://troybrant.net/blog/2010/02/adding-firstobject-to-nsarray/#comments</comments>
		<pubDate>Mon, 08 Feb 2010 21:44:58 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[category]]></category>
		<category><![CDATA[foundation]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=66</guid>
		<description><![CDATA[NSArray has a lastObject method. So, of course there is a matching firstObject method, right? Nope. If that oversight annoys you to no end, drop this simple category in your code, and say goodbye to all those ugly [array objectAtIndex:0]&#8216;s. // NSArray+FirstObject.h #import &#60;Foundation/Foundation.h&#62; @interface NSArray (FirstObject) &#45; (id)firstObject; @end // NSArray+FirstObject.m #import &#34;NSArray+FirstObject.h&#34; @implementation [...]]]></description>
			<content:encoded><![CDATA[<p><code>NSArray</code> has a <code>lastObject</code> method. So, of course there is a matching <code>firstObject</code> method, right?</p>
<p>Nope.</p>
<p>If that oversight annoys you to no end, drop this simple <a href="http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocCategories.html#//apple_ref/doc/uid/TP30001163-CH20-SW1">category</a> in your code, and say goodbye to all those ugly <code>[array objectAtIndex:0]</code>&#8216;s.</p>
<div class="code">
<span class="comment">// NSArray+FirstObject.h</span></p>
<p><span class="preprocessor">#import <span class="string">&lt;Foundation/Foundation.h&gt;</span></span> </p>
<p><span class="directive">@interface</span> NSArray (FirstObject) </p>
<p>&#45; (<span class="custom_type">id</span>)firstObject;</p>
<p><span class="directive">@end</span> </p>
</div>
<div class="code">
<span class="comment">// NSArray+FirstObject.m</span></p>
<p><span class="preprocessor">#import <span class="string">&quot;NSArray+FirstObject.h&quot;</span></span> </p>
<p><span class="directive">@implementation</span> NSArray (FirstObject) </p>
<p>&#45; (<span class="custom_type">id</span>)firstObject<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> ([<span class="keyword">self</span> <span class="method_call">count</span>] <span class="var">&gt;</span> <span class="number">0</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> [<span class="keyword">self</span> <span class="method_call">objectAtIndex</span>:<span class="number">0</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">nil</span>;<br />
} </p>
<p><span class="directive">@end</span> </p>
</div>
<p>When you need the first element of an array (a common enough task) just use <code>firstObject</code>:</p>
<div class="code">
<span class="preprocessor">#import <span class="string">&quot;NSArray+FirstObject.h&quot;</span></span> </p>
<p><span class="built_in_type">NSArray</span> *shows = [<span class="built_in_type">NSArray</span> <span class="method_call">arrayWithObjects</span>:<span class="string">@"Chuck"</span>, <span class="string">@"Caprica"</span>, <span class="string">@"LOST"</span>, <span class="keyword">nil</span>];</p>
<p><span style="text-decoration: line-through;"><span class="built_in_type">NSString</span> *<span class="var">firstShow</span> = [<span class="var">shows</span> <span class="method_call">objectAtIndex</span>:<span class="number">0</span>];</span><br />
<span class="built_in_type">NSString</span> *<span class="var">firstShow</span> = [<span class="var">shows</span> <span class="method_call">firstObject</span>];</p>
</div>
<p>Ah, much better.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/02/adding-firstobject-to-nsarray/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Detecting Bad CoreLocation Data</title>
		<link>http://troybrant.net/blog/2010/02/detecting-bad-corelocation-data/</link>
		<comments>http://troybrant.net/blog/2010/02/detecting-bad-corelocation-data/#comments</comments>
		<pubDate>Sat, 06 Feb 2010 06:21:57 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[cllocation]]></category>
		<category><![CDATA[cllocationmanager]]></category>
		<category><![CDATA[corelocation]]></category>
		<category><![CDATA[gps]]></category>
		<category><![CDATA[runmonster]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=64</guid>
		<description><![CDATA[CoreLocation can (and will) give you poor location data. Over the course of developing RunMonster, I have become painfully aware of this fact. It turns out, though, that you can detect and discard the most egregiously bad location data using a few simple tests. When a new point comes in, it is invalid and can [...]]]></description>
			<content:encoded><![CDATA[<p><code>CoreLocation</code> can (and will) give you poor location data. Over the course of developing <a href="http://www.runmonster.com/">RunMonster</a>, I have become painfully aware of this fact. It turns out, though, that you can detect and discard the most egregiously bad location data using a few simple tests.</p>
<p>When a new point comes in, it is invalid and can be discarded if it matches any of the following criteria:</p>
<ul>
<li>The location is nil.</li>
<li>The location&#8217;s horizontalAccuracy is &lt; 0.</li>
<li>The timestamp of the new location is earlier than the timestamp of the previous location, indicating the points came in out of order.</li>
<li>The timestamp of the new location is set to a time before your app was even initialized.</li>
</ul>
<p>In regards to the last bullet, the <code>CoreLocation</code> framework seems to cache and report points from the last time the GPS unit was used. For instance, if you last ran the GPS in Montana and then open your app in Georgia, the first point could be the last cached point from Montana. If you are running a distance-tracking application, then your app would merrily add thousands of miles to your total distance.</p>
<h2>The Code</h2>
<p>The helper method below checks for these four cases to determine if the newly reported location is valid or not:</p>
<div class="code">
&#45; (<span class="primitive">BOOL</span>)isValidLocation:(<span class="built_in_type">CLLocation</span> *)newLocation<br />
&nbsp;&nbsp;&nbsp;&nbsp;withOldLocation:(<span class="built_in_type">CLLocation</span> *)oldLocation<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Filter out nil locations</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (!<span class="var">newLocation</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">NO</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Filter out points by invalid accuracy</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">newLocation</span>.<span class="property_call">horizontalAccuracy</span> <span class="var">&lt;</span> <span class="number">0</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">NO</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Filter out points that are out of order</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSTimeInterval</span> <span class="var">secondsSinceLastPoint</span> =<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">newLocation</span>.<span class="property_call">timestamp</span> <span class="method_call">timeIntervalSinceDate</span>:<span class="var">oldLocation</span>.<span class="property_call">timestamp</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">secondsSinceLastPoint</span> <span class="var">&lt;</span> <span class="number">0</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">NO</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Filter out points created before the manager was initialized</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSTimeInterval</span> <span class="var">secondsSinceManagerStarted</span> =<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">newLocation</span>.<span class="property_call">timestamp</span> <span class="method_call">timeIntervalSinceDate</span>:<span class="ivar">locationManagerStartDate</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">secondsSinceManagerStarted</span> <span class="var">&lt;</span> <span class="number">0</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">NO</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// The newLocation is good to use</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="keyword">YES</span>;<br />
}
</div>
<p><code>locationManagerStartDate</code> is an <code>NSDate</code> that records when the <code>CLLocationManager</code> is initialized:</p>
<div class="code">
<span class="ivar">locationManager</span> = [[<span class="built_in_type">CLLocationManager</span> <span class="method_call">alloc</span>] <span class="method_call">init</span>];<br />
<span class="ivar">locationManager</span>.<span class="property_call">delegate</span> = <span class="keyword">self</span>;<br />
[<span class="ivar">locationManager</span> <span class="method_call">startUpdatingLocation</span>];</p>
<p><span class="ivar">locationManagerStartDate</span> = [[<span class="built_in_type">NSDate</span> <span class="method_call">date</span>] <span class="method_call">retain</span>];
</div>
<h2>That&#8217;s a Wrap</h2>
<p>If you are developing any kind of distance-tracking application that uses the phone&#8217;s GPS, you should definitely consider using the filters above. These particular error cases were discovered through trial and error, and as Apple improves <code>CoreLocation</code>, they may very well fix these problems. However, these filters have helped <a href="http://www.runmonster.com/">RunMonster</a> avoid the wacky GPS issues that plague many distance-tracking applications.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/02/detecting-bad-corelocation-data/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>MKMapView and Zoom Levels: A Visual Guide</title>
		<link>http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/</link>
		<comments>http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/#comments</comments>
		<pubDate>Sun, 31 Jan 2010 00:11:35 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[mapkit]]></category>
		<category><![CDATA[maps]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=8</guid>
		<description><![CDATA[So, how exactly does the code provided in the previous post work? What follows is a visual explanation of Google Maps, zoom levels, and how you go about adding support for zoom levels to the MKMapView class. Round to Flat This is planet Earth: As you may know, it is round. To create a map [...]]]></description>
			<content:encoded><![CDATA[<p>So, how exactly does the code provided in the <a href="http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/">previous post</a> work? What follows is a visual explanation of Google Maps, zoom levels, and how you go about adding support for zoom levels to the <code>MKMapView</code> class.</p>
<h2>Round to Flat</h2>
<p>This is planet Earth:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/1-globe.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/1-globe-300x300.png" alt="" title="The Earth" width="300" height="300" class="aligncenter size-medium wp-image-17" /></a></p>
<p>As you may know, it is round.</p>
<p>To create a map of the Earth, the curved surface must be projected onto a flat surface. There are many <a href="http://en.wikipedia.org/wiki/Map_projection">map projections</a> that attempt to flatten the Earth. There are distortions inherent to every projection, but each map projection aims to preserve at least one quality from the original curved representation.</p>
<p>Some projections preserve area, such as the <a href="http://en.wikipedia.org/wiki/Mollweide_projection">Mollweide projection</a>:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/3-mollweide-projection.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/3-mollweide-projection-300x150.png" alt="" title="Mollweide Projection" width="300" height="150" class="aligncenter size-medium wp-image-19" /></a></p>
<p><a href="http://en.wikipedia.org/wiki/Equirectangular_projection">Equirectangluar projections</a> preserve distance between the <a href="http://en.wikipedia.org/wiki/Meridian_(geography)">meridians</a>:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/4-equirectangular-projection.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/4-equirectangular-projection-300x150.png" alt="" title="Equirectangular Projection" width="300" height="150" class="aligncenter size-medium wp-image-20" /></a></p>
<p>The  <a href="http://en.wikipedia.org/wiki/Mercator_projection">Mercator projection</a> stretches out the poles in order to preserve locally measured angles:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/2-mercator-projection.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/2-mercator-projection-300x232.png" alt="" title="Mercator Projection" width="300" height="232" class="aligncenter size-medium wp-image-21" /></a></p>
<p>Google uses the Mercator projection to render <a href="http://maps.google.com/?ie=UTF8&#038;hq=&#038;h&#038;ll=1.406109,-35.859375&#038;spn=175.646868,360&#038;z=1">Google Maps</a>:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/5-mercator-equals-google.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/5-mercator-equals-google-1024x442.png" alt="" title="The Mercator projection is used by Google Maps" width="600" height="258" class="aligncenter size-large wp-image-25" /></a></p>
<h2>Mercator Math</h2>
<p>The Mercator projection converts latitude (φ) and longitude (λ) coordinates to pixel values. It uses math:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/6-mercator-equations.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/6-mercator-equations.png" alt="" title="Equations for converting latitude and longitude coordinates to pixels" width="213" height="208" class="aligncenter size-full wp-image-26" /></a></p>
<p>You don&#8217;t have to understand the math<a href="http://theoatmeal.com/comics/semicolon">;</a> just know that it converts latitudes and longitudes to pixels.</p>
<p>But, where are these pixels? Well, it depends on your zoom level.</p>
<h2>Zoom Levels</h2>
<p>At zoom level 0, Google displays the world in a single 256 pixel by 256 pixel tile:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/7-zoom-level-0.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/7-zoom-level-0.png" alt="" title="Zoom level 0" width="600" height="480" class="aligncenter size-full wp-image-28" /></a></p>
<p>At zoom level 1, Google doubles the area of the map while keeping the tile size constant. So, the map grows to 512 pixels by 512 pixels and uses four tiles:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/8-zoom-level-1.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/8-zoom-level-1.png" alt="" title="Zoom level 1" width="600" height="480" class="aligncenter size-full wp-image-29" /></a></p>
<p>At zoom level 2, Google doubles the area again. The map grows to 1024 pixels by 1024 pixels and uses sixteen tiles:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/9-zoom-level-2.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/9-zoom-level-2.png" alt="" title="Zoom level 2" width="600" height="480" class="aligncenter size-full wp-image-30" /></a></p>
<p>The pixel area continues to double at each zoom level, and when zoom level 20 is reached, the map is 536,870,912 pixels by 536,870,912 pixels. It has so many tiles we won&#8217;t bother to count them:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/10-zoom-level-20.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/10-zoom-level-20.png" alt="" title="Zoom level 20" width="600" height="480" class="aligncenter size-full wp-image-32" /></a></p>
<h2>Latitudes and Longitudes to Pixels</h2>
<p>As part of the <a href="http://www.appelsiini.net/2008/10/simple-static-maps-with-php">PHP Static Maps project</a>, <a href="http://www.appelsiini.net/">Mike Tuupola</a> wrote <a href="http://github.com/tuupola/php_google_maps/blob/master/Google/Maps/Mercator.php">some code</a> that converts latitudes and longitudes to pixels at zoom level 20. The code is easily ported to Objective-C:</p>
<div class="code">
<span class="comment">// Convert latitude and longitude to pixel values at zoom level 20</span></p>
<p><span class="preprocessor">#define MERCATOR_OFFSET <span class="number">268435456</span></span> <span class="comment">/* (total pixels at zoom level 20) / 2 */</span><br />
<span class="preprocessor">#define MERCATOR_RADIUS <span class="number">85445659.44705395</span></span> <span class="comment">/* MERCATOR_OFFSET / pi */</span></p>
<p>x = <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> + <span class="constant">MERCATOR_RADIUS</span> * <span class="var">longitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>);<br />
y = <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> &#45; <span class="constant">MERCATOR_RADIUS</span> * <span class="built_in_type">logf</span>((<span class="number">1</span> + <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>)) / (<span class="number">1</span> &#45; <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>))) / <span class="number">2</span>.<span class="number">0</span>);
</div>
<p>To be honest, I haven&#8217;t taken the time to wrap my head around how this code works. But, knowing that it <em>does</em> work, we can now take any latitude and longitude and figure out its pixel coordinates at zoom level 20. For instance, here are the pixel coordinates of several cities around the world:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/11-pixel-location-of-cities.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/11-pixel-location-of-cities.png" alt="" title="Pixel location of cities at zoom level 20" width="600" height="480" class="aligncenter size-full wp-image-33" /></a></p>
<h2>Add an iPhone</h2>
<p>Say we place an iPhone on top of Anchorage, Alaska at zoom level 20:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/12-iphone-on-anchorage-zoom-20.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/12-iphone-on-anchorage-zoom-20.png" alt="" title="Anchorage, Alaska at zoom level 20" width="549" height="480" class="aligncenter size-full wp-image-60" /></a></p>
<p>In the iPhone shown above, the map size is 320 pixels by 460 pixels. Since we know the map dimensions and center coordinate in pixels, we can easily compute the pixel coordinates of the top-left corner relative to the center pixel coordinate:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/13-top-left-zoom-201.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/13-top-left-zoom-201.png" alt="" title="Relative position of the top-left corner" width="296" height="480" class="aligncenter size-full wp-image-61" /></a></p>
<p>We can find the relative position of the top-right and bottom-left pixel coordinates as well:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/14-ritght-and-bottom-zoom-201.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/14-ritght-and-bottom-zoom-201.png" alt="" title="Relative position of the top-right and bottom-left corners" width="296" height="480" class="aligncenter size-full wp-image-62" /></a></p>
<p>The PHP Static Maps code also provides <a href="http://github.com/tuupola/php_google_maps/blob/master/Google/Maps/Mercator.php">code</a> to go from pixels at zoom level 20 to latitudes and longitudes:</p>
<div class="code">
<span class="comment">// Convert pixel values at zoom level 20 to latitude and longitude</span></p>
<p>latitude = (<span class="constant">M_PI</span> / <span class="number">2</span>.<span class="number">0</span> &#45; <span class="number">2</span>.<span class="number">0</span> * <span class="built_in_type">atan</span>(<span class="built_in_type">exp</span>((<span class="built_in_type">round</span>(<span class="var">pixelY</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>))) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;<br />
longitude = ((<span class="built_in_type">round</span>(<span class="var">pixelX</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;
</div>
<p>We can use this code to convert the corners from pixel coordinates to latitudes and longitudes:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/15-lat-lng-corners-zoom-201.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/15-lat-lng-corners-zoom-201.png" alt="" title="Corner pixels are converted to latitude and longitudes" width="511" height="480" class="aligncenter size-full wp-image-63" /></a></p>
<p>As shown above, using the corner coordinates, we can compute the latitudinal and longitudinal distances. These distances are exactly what we need to construct an <code>MKCoordinateSpan</code>. That span, in turn, is used to initialize the region property of an <code>MKMapView</code>:</p>
<div class="code">
<span class="comment">// Create an MKCoordinateSpan to initialize the map&#8217;s region</span></p>
<p><span class="built_in_type">MKCoordinateSpan</span> <span class="var">span</span> = <span class="built_in_type">MKCoordinateSpanMake</span>(<span class="var">latitudeDelta</span>, <span class="var">longitudeDelta</span>);<br />
<span class="built_in_type">MKCoordinateRegion</span> <span class="var">region</span> = <span class="built_in_type">MKCoordinateRegionMake</span>(<span class="var">centerCoordinate</span>, <span class="var">span</span>);<br />
[<span class="ivar">mapView</span> <span class="method_call">setRegion</span>:<span class="var">region</span> <span class="method_call">animated</span>:<span class="keyword">NO</span>];
</div>
<p>And you&#8217;re done!&#8230;That is, if you want to see zoom level 20. What do you do when your user wants to see the map at zoom level 19 instead of 20?</p>
<h2>Scaling using Zoom Levels</h2>
<p>Relative to zoom level 20, zooming out one level doubles the area visible on the map.</p>
<p>For example, consider the image below. On the left is Anchorage at zoom level 19, and on the right are the 4 iPhones at zoom level 20 it would take to display the same amount of area:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/16-anchorage-zoom-19.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/16-anchorage-zoom-19.png" alt="" title="On the left, Anchorage at zoom level 19. On the right, the number of iPhones at zoom level 20 it would take to display the same area." width="560" height="401" class="aligncenter size-full wp-image-52" /></a></p>
<p>If we move up another level, the area doubles again. Consider the following image. On the left is Anchorage at zoom level 18, and on the right are the 16 iPhones at zoom level 20 it would take to display the same amount of area:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/17-anchorage-zoom-18.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/17-anchorage-zoom-18.png" alt="" title="On the left, Anchorage at zoom level 18. On the right, the number of iPhones at zoom level 20 it would take to display the same area." width="560" height="405" class="aligncenter size-full wp-image-51" /></a></p>
<p>Since the area doubles at each zoom level, we can define the following exponential relationship between the zoom level and the area covered by the map:</p>
<div class="code">
<span class="comment">// Compute a scaling factor that will take us from any zoom level to zoom level 20</span></p>
<p><span class="built_in_type">NSInteger</span> <span class="var">zoomExponent</span> = <span class="number">20</span> &#45; <span class="var">zoomLevel</span>;<br />
<span class="primitive">double</span> <span class="var">zoomScale</span> = <span class="var">pow</span>(<span class="number">2</span>, <span class="var">zoomExponent</span>);</p>
<p><span class="primitive">double</span> <span class="var">scaledMapWidth</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">width</span> * <span class="var">zoomScale</span>;<br />
<span class="primitive">double</span> <span class="var">scaledMapHeight</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">height</span> * <span class="var">zoomScale</span>;
</div>
<p>For instance, here is Anchorage at zoom levels 20, 19, and 18. The map&#8217;s width and height in pixels are unaltered:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/18-anchorage-zoom-20-19-18.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/18-anchorage-zoom-20-19-18.png" alt="" title="Anchorage at zoom levels 20, 19, and 18." width="560" height="349" class="aligncenter size-full wp-image-53" /></a></p>
<p>After computing the zoom scale factor, we can apply it to each map to determine its dimensions at zoom level 20:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/19-anchorage-zoom-20-20-20.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/19-anchorage-zoom-20-20-20.png" alt="" title="The result of converting zoom levels 20, 19, and 18 to zoom level 20." width="560" height="358" class="aligncenter size-full wp-image-54" /></a></p>
<p>After we compute these new dimensions, we plug them into the algorithm for finding the coordinates of the map corners.</p>
<h2>An Example: Zoom Level 18</h2>
<p>For instance, say we take the map at zoom level 18:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/20-zoom-18-to-20-matrix.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/20-zoom-18-to-20-matrix.png" alt="" title="Zoom level 18 scaled to zoom level 20" width="269" height="325" class="aligncenter size-full wp-image-55" /></a></p>
<p>Let&#8217;s drop the matrix of phones but keep the scaled width and height:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/21-zoom-18-to-20-no-matrix.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/21-zoom-18-to-20-no-matrix.png" alt="" title="Zoom level 18 scaled to zoom level 20" width="309" height="480" class="aligncenter size-full wp-image-56" /></a></p>
<p>We find the top-left corner just like we did before, except now we use the scaled width and height:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/22-zoom-18-top-left.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/22-zoom-18-top-left.png" alt="" title="Relative position of top-left corner using scaled dimensions" width="310" height="480" class="aligncenter size-full wp-image-57" /></a></p>
<p>Similarly, we use the scaled width and height for finding the top-right and bottom-left corners as well:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/23-zoom-18-other-corners.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/23-zoom-18-other-corners.png" alt="" title="Relative position of top-right and bottom-left corners using scaled dimensions" width="309" height="480" class="aligncenter size-full wp-image-58" /></a></p>
<p>Using the pixel and latitude and pixel and longitude helper methods, we can compute the coordinates of the corners and the distance between them:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/24-zoom-18-lat-lng-corners.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/24-zoom-18-lat-lng-corners.png" alt="" title="Pixels at zoom level 20 are converted to latitudes and longitudes" width="511" height="480" class="aligncenter size-full wp-image-59" /></a></p>
<p>These delta values are used to initialize the map&#8217;s region property, and the map zooms to the level you specify.</p>
<h2>That&#8217;s a Wrap</h2>
<p>Be sure to check out the <a href="">previous post</a> for the full code that adds support for zoom levels to <code>MKMapView</code>.</p>
<p>If you are interested in learning more from someone much smarter than I am, check out these posts from <a href="http://cfis.savagexi.com/pages/about">Charlie Savage</a>, a programmer and cartographer extraordinaire:</p>
<ul>
<li><a href="http://cfis.savagexi.com/2006/05/03/google-maps-deconstructed">Google Maps Deconstructed</a></li>
<li><a href="http://cfis.savagexi.com/2006/05/05/google-maps-revisited">Google Maps Revisted</a></li>
<li><a href="http://cfis.savagexi.com/2006/06/30/mouse-coordinates-to-lat-long">Mouse Coordinates to Lat/Long</a></li>
</ul>
<p>Much of what I know about maps is from these articles, and I highly recommended checking them out if you want to learn more about how Google Maps works under the hood.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Set the Zoom Level of an MKMapView</title>
		<link>http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/</link>
		<comments>http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/#comments</comments>
		<pubDate>Fri, 22 Jan 2010 22:59:25 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[mapkit]]></category>
		<category><![CDATA[maps]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=6</guid>
		<description><![CDATA[If you have ever built a web application using the Google Maps API, you are likely intimately familiar with this line of code: map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13); The setCenter JavaScript method takes in the center coordinate and a zoom level. The zoom level, as you might expect, determines how far the map should zoom in. [...]]]></description>
			<content:encoded><![CDATA[<p>If you have ever built a web application using the Google Maps API, you are likely intimately familiar with this line of code:</p>
<div class="code">
<span class="ivar">map</span>.setCenter(<span class="keyword">new</span> google.maps.<span class="built_in_type">LatLng</span>(<span class="number">37.4419</span>, -<span class="number">122</span>.<span class="number">1419</span>), <span class="number">13</span>);
</div>
<p>The <code>setCenter</code> JavaScript method takes in the center coordinate and a zoom level. The zoom level, as you might expect, determines how far the map should zoom in. The zoom level ranges from 0 (all the way zoomed out) to some upper value (all the way zoomed in). The max zoom level for a particular area depends on the location (for instance, you can&#8217;t zoom in too far on North Korea) and the map type (default, satellite, hybrid, terrain, etc). Typically, the max zoom level for an area is somewhere between 15 &#8211; 21.</p>
<p>Unfortunately, <code>MapKit</code> on the iPhone does not include a way to set the zoom level. Instead, the zoom level is set <em>implicitly</em> by defining the <code>MKCoordinateRegion</code> of the map&#8217;s viewport. When initializing the region, you specify the amount of distance the map displays in the horizontal and vertical directions. The zoom level is set implicitly based on these distance values.</p>
<p>Instead of dealing with this region business, I wrote a category that adds support for setting the zoom level of an <code>MKMapView</code> explicitly. In this post, I&#8217;ll give you code you can drop into your own projects and start using immediately. <a href="http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/">My next post</a> will detail exactly how it works.</p>
<h2>The Code</h2>
<p>Instead of force-feeding you everything I learned while working on this mini-project, I&#8217;ll give you the goods up front. The code below defines a category on <code>MKMapView</code> that gives you the ability to set the zoom level for your map:</p>
<div class="code">
<span class="comment">//  MKMapView+ZoomLevel.h</span> </p>
<p><span class="preprocessor">#import <span class="string">&lt;MapKit/MapKit.h&gt;</span></span> </p>
<p><span class="directive">@interface</span> MKMapView (ZoomLevel) </p>
<p>- (<span class="primitive">void</span>)setCenterCoordinate:(<span class="built_in_type">CLLocationCoordinate2D</span>)centerCoordinate<br />
&nbsp;&nbsp;&nbsp;&nbsp;zoomLevel:(<span class="built_in_type">NSUInteger</span>)zoomLevel<br />
&nbsp;&nbsp;&nbsp;&nbsp;animated:(<span class="primitive">BOOL</span>)animated;</p>
<p><span class="directive">@end</span>
</div>
<div class="code">
<span class="comment">//  MKMapView+ZoomLevel.m</span> </p>
<p><span class="preprocessor">#import <span class="string">&quot;MKMapView+ZoomLevel.h&quot;</span></span> </p>
<p><span class="preprocessor">#define MERCATOR_OFFSET <span class="number">268435456<br />
</span></span> <span class="preprocessor">#define MERCATOR_RADIUS <span class="number">85445659.44705395</p>
<p></span></span> <span class="directive">@implementation</span> MKMapView (ZoomLevel) </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark Map conversion methods</span> </p>
<p>- (<span class="primitive">double</span>)longitudeToPixelSpaceX:(<span class="primitive">double</span>)longitude<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> + <span class="constant">MERCATOR_RADIUS</span> * <span class="var">longitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>);<br />
} </p>
<p>- (<span class="primitive">double</span>)latitudeToPixelSpaceY:(<span class="primitive">double</span>)latitude<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> &#45; <span class="constant">MERCATOR_RADIUS</span> * <span class="built_in_type">logf</span>((<span class="number">1</span> + <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>)) / (<span class="number">1</span> &#45; <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>))) / <span class="number">2</span>.<span class="number">0</span>);<br />
} </p>
<p>- (<span class="primitive">double</span>)pixelSpaceXToLongitude:(<span class="primitive">double</span>)pixelX<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> ((<span class="built_in_type">round</span>(<span class="var">pixelX</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;<br />
} </p>
<p>- (<span class="primitive">double</span>)pixelSpaceYToLatitude:(<span class="primitive">double</span>)pixelY<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> (<span class="constant">M_PI</span> / <span class="number">2</span>.<span class="number">0</span> &#45; <span class="number">2</span>.<span class="number">0</span> * <span class="built_in_type">atan</span>(<span class="built_in_type">exp</span>((<span class="built_in_type">round</span>(<span class="var">pixelY</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>))) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;<br />
} </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark Helper methods</span> </p>
<p>- (<span class="built_in_type">MKCoordinateSpan</span>)coordinateSpanWithMapView:(<span class="built_in_type">MKMapView</span> *)mapView<br />
&nbsp;&nbsp;&nbsp;&nbsp;centerCoordinate:(<span class="built_in_type">CLLocationCoordinate2D</span>)centerCoordinate<br />
&nbsp;&nbsp;&nbsp;&nbsp;andZoomLevel:(<span class="built_in_type">NSUInteger</span>)zoomLevel<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// convert center coordiate to pixel space</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">centerPixelX</span> = [<span class="keyword">self</span> <span class="method_call">longitudeToPixelSpaceX</span>:<span class="var">centerCoordinate</span>.<span class="property_call">longitude</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">centerPixelY</span> = [<span class="keyword">self</span> <span class="method_call">latitudeToPixelSpaceY</span>:<span class="var">centerCoordinate</span>.<span class="property_call">latitude</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// determine the scale value from the zoom level</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSInteger</span> <span class="var">zoomExponent</span> = <span class="number">20</span> &#45; <span class="var">zoomLevel</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">zoomScale</span> = <span class="var">pow</span>(<span class="number">2</span>, <span class="var">zoomExponent</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// scale the map&#8217;s size in pixel space</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CGSize</span> <span class="var">mapSizeInPixels</span> = <span class="var">mapView</span>.<span class="property_call">bounds</span>.<span class="property_call">size</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">scaledMapWidth</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">width</span> * <span class="var">zoomScale</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">scaledMapHeight</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">height</span> * <span class="var">zoomScale</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// figure out the position of the top-left pixel</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">topLeftPixelX</span> = <span class="var">centerPixelX</span> &#45; (<span class="var">scaledMapWidth</span> / <span class="number">2</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">topLeftPixelY</span> = <span class="var">centerPixelY</span> &#45; (<span class="var">scaledMapHeight</span> / <span class="number">2</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// find delta between left and right longitudes</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">minLng</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceXToLongitude</span>:<span class="var">topLeftPixelX</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">maxLng</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceXToLongitude</span>:<span class="var">topLeftPixelX</span> + <span class="var">scaledMapWidth</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">longitudeDelta</span> = <span class="var">maxLng</span> &#45; <span class="var">minLng</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// find delta between top and bottom latitudes</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">minLat</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceYToLatitude</span>:<span class="var">topLeftPixelY</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">maxLat</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceYToLatitude</span>:<span class="var">topLeftPixelY</span> + <span class="var">scaledMapHeight</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">latitudeDelta</span> = -<span class="number">1</span> * (<span class="var">maxLat</span> &#45; <span class="var">minLat</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// create and return the lat/lng span</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">MKCoordinateSpan</span> <span class="var">span</span> = <span class="built_in_type">MKCoordinateSpanMake</span>(<span class="var">latitudeDelta</span>, <span class="var">longitudeDelta</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="var">span</span>;<br />
} </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark Public methods</span> </p>
<p>- (<span class="primitive">void</span>)setCenterCoordinate:(<span class="built_in_type">CLLocationCoordinate2D</span>)centerCoordinate<br />
&nbsp;&nbsp;&nbsp;&nbsp;zoomLevel:(<span class="built_in_type">NSUInteger</span>)zoomLevel<br />
&nbsp;&nbsp;&nbsp;&nbsp;animated:(<span class="primitive">BOOL</span>)animated<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// clamp large numbers to 28</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="var">zoomLevel</span> = <span class="constant">MIN</span>(<span class="var">zoomLevel</span>, <span class="number">28</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// use the zoom level to compute the region</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">MKCoordinateSpan</span> <span class="var">span</span> = [<span class="keyword">self</span> <span class="method_call">coordinateSpanWithMapView</span>:<span class="keyword">self</span> <span class="method_call">centerCoordinate</span>:<span class="var">centerCoordinate</span> <span class="method_call">andZoomLevel</span>:<span class="var">zoomLevel</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">MKCoordinateRegion</span> <span class="var">region</span> = <span class="built_in_type">MKCoordinateRegionMake</span>(<span class="var">centerCoordinate</span>, <span class="var">span</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// set the region like normal</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">setRegion</span>:<span class="var">region</span> <span class="method_call">animated</span>:<span class="var">animated</span>];<br />
} </p>
<p><span class="directive">@end</span> </p>
</div>
<p>If you&#8217;re wondering why this works, check out <a href="http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/">my next post</a> where I describe in gruesome detail the math behind the code.</p>
<p>On the other hand, if you don&#8217;t really care <em>how</em> it works, just that it <em>does</em> work, copy and paste away, my friend.</p>
<h2>Test the Code</h2>
<p>To test the category, assuming you have a view controller with an <code>MKMapView</code> instance, you can use the following code:</p>
<div class="code">
<span class="comment">// ZoomLevelTestViewController.m</span></p>
<p><span class="preprocessor">#import <span class="string">&quot;MKMapView+ZoomLevel.h&quot;</span></span> </p>
<p><span class="preprocessor">#define GEORGIA_TECH_LATITUDE <span class="number">33.777328</span></span><br />
<span class="preprocessor">#define GEORGIA_TECH_LONGITUDE <span class="number">-84.397348</span></span> </p>
<p><span class="preprocessor">#define ZOOM_LEVEL <span class="number">14</span></span></p>
<p>- (<span class="primitive">void</span>)viewDidAppear:(<span class="primitive">BOOL</span>)animated<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">super</span> <span class="method_call">viewDidAppear</span>:<span class="var">animated</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationCoordinate2D</span> <span class="var">centerCoord</span> = { <span class="constant">GEORGIA_TECH_LATITUDE</span>, <span class="constant">GEORGIA_TECH_LONGITUDE</span> };<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">mapView</span> <span class="method_call">setCenterCoordinate</span>:<span class="var">centerCoord</span> <span class="method_call">zoomLevel</span>:<span class="constant">ZOOM_LEVEL</span> <span class="method_call">animated</span>:<span class="keyword">NO</span>];<br />
}
</div>
<p>And, viola! Your map should zoom in to where you can see the Georgia Tech campus.</p>
<h2>Results</h2>
<p>To verify that the zoom level is set correctly, I wrote a simple web-based Google Maps application to make sure the web and native zoom levels matched. Both apps were centered at {33.777328, -84.397348} (Georgia Tech). In the images below, the iPhone on the left is running the native app and the iPhone on the right is running the web app:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-2.png" rel="lightbox[6]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-2-300x255.png" alt="Zoom Level 2: Native app on the left. Web app on the right." title="Zoom Level 2: Native app on the left. Web app on the right." width="300" height="255" class="aligncenter size-medium wp-image-14" /></a></p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-10.png" rel="lightbox[6]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-10-300x255.png" alt="Zoom Level 10: Native app on the left. Web app on the right." title="Zoom Level 10: Native app on the left. Web app on the right." width="300" height="255" class="aligncenter size-medium wp-image-15" /></a></p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-18.png" rel="lightbox[6]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-18-300x255.png" alt="Zoom Level 18: Native app on the left. Web app on the right." title="Zoom Level 18: Native app on the left. Web app on the right." width="300" height="255" class="aligncenter size-medium wp-image-16" /></a></p>
<p>As you can see, they match.</p>
<h2>That&#8217;s a Wrap</h2>
<p>By using the <code>MKMapView+ZoomLevel</code> category, you won&#8217;t have to bother setting the region at all. If you are like me and have no intuition for how to set the map&#8217;s region, then hopefully this code will give you a bit more control in setting your map&#8217;s zoom level.</p>
<p><a href="http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/">Next time</a>, I&#8217;ll go over exactly why the code above works. But, for now, enjoy the freedom to set zoom levels!</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/feed/</wfw:commentRss>
		<slash:comments>42</slash:comments>
		</item>
		<item>
		<title>Invalid Product IDs</title>
		<link>http://troybrant.net/blog/2010/01/invalid-product-ids/</link>
		<comments>http://troybrant.net/blog/2010/01/invalid-product-ids/#comments</comments>
		<pubDate>Sun, 17 Jan 2010 21:16:09 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[in app purchases]]></category>
		<category><![CDATA[storekit]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=5</guid>
		<description><![CDATA[Do you have an invalid product ID that won&#8217;t go away? Good thing the StoreKit API provides error codes and detailed error information explaining why the ID is invalid. Oh, what&#8217;s that? There are no error codes or error details of any kind when you have an invalid product ID, you say? Bah, silly me. [...]]]></description>
			<content:encoded><![CDATA[<p>Do you have an invalid product ID that won&#8217;t go away? Good thing the <code>StoreKit</code> API provides error codes and detailed error information explaining why the ID is invalid.</p>
<p>Oh, what&#8217;s that? There are no error codes or error details of any kind when you have an invalid product ID, you say? Bah, silly me.</p>
<p>To save you the pain of exhaustively searching the web for the cause of your error, here is a checklist of everything I have stumbled across that can cause an invalid product ID. Make sure you can answer &#8220;Yes&#8221; to each of these questions:</p>
<ul>
<li>Have you enabled In-App Purchases for your App ID?</li>
<li>Have you checked Cleared for Sale for your product?</li>
<li>Have you submitted (and optionally rejected) your application binary?</li>
<li>Does your project&#8217;s .plist Bundle ID match your App ID?</li>
<li>Have you generated and installed a new provisioning profile for the new App ID?</li>
<li>Have you configured your project to code sign using this new provisioning profile?</li>
<li>Are you building for iPhone OS 3.0 or above?</li>
<li>Are you using the full product ID when when making an <code>SKProductRequest</code>?</li>
<li>Have you waited several hours since adding your product to iTunes Connect?</li>
<li>Are your bank details active on iTunes Connect? (via <a href="#comment-22">Mark</a>)</li>
<li>Have you tried deleting the app from your device and reinstalling? (via <a href="#comment-356">Hector</a>, <a href="#comment-883">S3B</a>, <a href="#comment-1001">Alex O</a>, <a href="#comment-1070">Joe</a>, and <a href="#comment-1243">Alberto</a>)
<li>Is your device jailbroken? If so, you need to revert the jailbreak for IAP to work. (via <a href="#comment-5763">oh my god</a>, <a href="#comment-5921">Roman</a>, and <a href="#comment-6210">xfze</a>)
</ul>
<p>If you answered &#8220;No&#8221; to any one of these questions, there&#8217;s your problem.</p>
<p>If you answered &#8220;Yes&#8221; for each of these questions and you still have an invalid product ID, then you have a problem I haven&#8217;t seen before. Check out the links in the next section, several of which are Developer Forum posts that were especially helpful in my hunt for debugging invalid product IDs.</p>
<h2>Resources for Debugging an Invalid Product</h2>
<p>Here are the docs and resources I used to debug invalid product IDs:</p>
<ul>
<li>Official In App Purchase Programming Guide
<ul>
<li><a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html">http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html</a></li>
</ul>
</li>
<li>Official iTunes Connect Developer Guide (In App Purchases starting on page 52)
<ul>
<li><a href="https://itunesconnect.apple.com/docs/iTunesConnect_DeveloperGuide.pdf">https://itunesconnect.apple.com/docs/iTunesConnect_DeveloperGuide.pdf</a></li>
</ul>
</li>
<li>Developer Forums &#8211; Best thread on debugging in-app purchases
<ul>
<li><a href="https://devforums.apple.com/thread/23344?start=0&amp;tstart=0">https://devforums.apple.com/thread/23344?start=0&amp;tstart=0</a></li>
</ul>
</li>
<li>Developer Forums &#8211; Invalid product ID with solution
<ul>
<li><a href="https://devforums.apple.com/message/146017">https://devforums.apple.com/message/146017</a></li>
</ul>
</li>
<li>Developer Forums &#8211; Invalid product ID with solution
<ul>
<li><a href="https://devforums.apple.com/message/136985">https://devforums.apple.com/message/136985</a></li>
</ul>
</li>
<li>Developer Forums &#8211; Invalid product ID with solution
<ul>
<li><a href="https://devforums.apple.com/message/147790">https://devforums.apple.com/message/147790</a></li>
</ul>
</li>
<li>Developer Forums &#8211; &#8220;in app purchase invalid&#8221; search results
<ul>
<li><a href="https://devforums.apple.com/search.jspa?q=in+app+purchase+invalid&amp;resultTypes=MESSAGE&amp;peopleEnabled=true&amp;dateRange=last90days&amp;username=&amp;numResults=50">https://devforums.apple.com/search.jspa?q=in+app+purchase+invalid&amp;resultTypes=MESSAGE&amp;peopleEnabled=true&amp;dateRange=last90days&amp;username=&amp;numResults=50</a></li>
</ul>
</li>
<li>Apple StoreKit support forums &#8211; Invalid product ID with several solutions
<ul>
<li><a href="http://discussions.apple.com/thread.jspa?messageID=10344191">http://discussions.apple.com/thread.jspa?messageID=10344191</a></li>
</ul>
</li>
</ul>
<p>If your solution for invalid product IDs wasn&#8217;t mentioned in the checklist above, please leave a comment detailing how you fixed the problem so I can keep the checklist up to date.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/01/invalid-product-ids/feed/</wfw:commentRss>
		<slash:comments>66</slash:comments>
		</item>
		<item>
		<title>In App Purchases: A Full Walkthrough</title>
		<link>http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/</link>
		<comments>http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/#comments</comments>
		<pubDate>Sun, 17 Jan 2010 21:07:24 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[in app purchases]]></category>
		<category><![CDATA[storekit]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=3</guid>
		<description><![CDATA[At first glance, adding in-app purchases seems like it would be a walk in the park. Apple provides plenty of documentation that should get developers up and running in no time. So, why is adding in-app purchases such a royal pain in the arse? Because, inevitably, something will go wrong. And when that moment arrives, [...]]]></description>
			<content:encoded><![CDATA[<p>At first glance, adding in-app purchases seems like it would be a walk in the park. Apple provides <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html">plenty</a> of <a href="https://itunesconnect.apple.com/docs/iTunesConnect_DeveloperGuide.pdf">documentation</a> that should get developers up and running in no time.</p>
<p>So, why is adding in-app purchases such a royal pain in the arse?</p>
<p>Because, inevitably, something will go wrong. And when that moment arrives, you&#8217;re screwed. Apple provides a beastly amount of documentation on in-app purchases, but they don&#8217;t provide the <em>right</em> kind of documentation. Nowhere is there mention of the setup steps you have to take to get in-app purchases to work. Nowhere is there a checklist you can reference if your <code>StoreKit</code> integration doesn&#8217;t work. Nowhere is there an <code>NSError</code> object that tell you exactly why your product ID is invalid.</p>
<p>You are left to flounder and flail like a wet noodle as you exhaustively try every possible solution on the web.</p>
<p>Losing days of productivity on this is ridiculous. To save you the pain and suffering I went through, this post details every step you need to take to implement in-app purchases. It&#8217;s detailed. It&#8217;s long. It&#8217;s probably overly-detailed and overly-long. But, unlike the Apple docs, it contains every single step necessary for any developer to implement in-app purchases.</p>
<p>Without further ado, let&#8217;s get started.</p>
<h2>Overview</h2>
<p>Ok, folks, here&#8217;s the secret to getting in-app purchases working: break it into two distinct steps:</p>
<ol>
<li>Create and fetch a product description</li>
<li>Purchase a product</li>
</ol>
<p>The first step is where you will likely run into problems. Once you can successfully fetch a product description in code, writing the code to purchase the product is cake.</p>
<p>We&#8217;ll tackle the product description step first.</p>
<h2>Create and Fetch a Product Description</h2>
<p>Here is a (very) rough overview of each step required to create a new product and fetch its description:</p>
<ol>
<li>Create a unique App ID</li>
<li>Generate and install a new provisioning profile</li>
<li>Update the bundle ID and code signing profile in Xcode</li>
<li>If you haven&#8217;t already, submit your application metadata in iTunes Connect</li>
<li>If you haven&#8217;t already, submit your application binary in iTunes Connect</li>
<li>Add a new product for in-app purchase</li>
<li>Write code for fetching the product description</li>
<li>Wait a few hours</li>
</ol>
<p>The code for fetching a product description is really simple. The setup steps, on the other hand, are rife with peril.</p>
<p><em>NOTE: You do NOT need to create an in-app test user in iTunes Connect to fetch a product description.</em></p>
<h3>1. Create a Unique App ID</h3>
<p>To support in-app purchases, your App ID <em>cannot</em> include a wildcard character (&#8220;*&#8221;).  To see if your App ID contains a wildcard, log in to <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a>, and navigate to the iPhone Developer Program Portal. Select &#8220;App IDs&#8221; from the menu on the left, and look for your App ID.</p>
<p>This is a unique App ID:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;7DW89RZKLY.com.runmonster.runmonsterfree</p>
<p>This is <em>not</em> a unique App ID:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;7DW89RZKLY.com.runmonster.*</p>
<p>If you don&#8217;t have a unique App ID, create one as follows:</p>
<ol>
<li>On the App IDs tab in the developer portal, select &#8220;New App ID&#8221;</li>
<li>Fill in the following information:</li>
<ul>
<li><strong>Display name</strong>: Pick a <em>different</em> App ID name than you were using before. You can&#8217;t edit or delete old App IDs, so just give your App ID a new name to avoid confusion.</li>
<li><strong>Prefix</strong>: Generate a new one, or choose an existing one if your app is one of a suite of apps that can <a href="http://developer.apple.com/iphone/library/samplecode/GenericKeychain/index.html">share data via the Keychain Services API</a></li>
<li><strong>Suffix</strong>: <em>com.companyname.appname</em> (this is the usual format &#8211; note lack of wildcard)</li>
</ul>
<li value="3">Click &#8220;Save&#8221;</li>
<li>Click the &#8220;Configure&#8221; link next to your App ID</li>
<li>Check box next to &#8220;Enable In App Purchase&#8221;</li>
<li>Click &#8220;Done&#8221;</li>
</ol>
<h3>2. Create a New Provisioning Profile</h3>
<p>Now that you have a new App ID, you need to generate a new provisioning profile that points to the App ID.</p>
<p>Here&#8217;s the painfully detailed step-by-step for generating and installing a new provisioning profile:</p>
<ol>
<li>In the iPhone Developer Portal, select the Provisioning tab on the left</li>
<li>Make sure you&#8217;re on the Development tab, and click &#8220;New Profile&#8221; in the top-right corner</li>
<li>Fill in the requested information, and point to the unique App ID you just created</li>
<li>If see &#8220;Pending&#8221; in the Actions column, just click the Development tab title to refresh</li>
<li>Click &#8220;Download&#8221; to pull down the new profile</li>
<li>Drag the profile onto the Xcode icon in the Dock to install</li>
<li>Alternatively, if you want to preserve the name of the provisioning profile on disk, you can install the profile manually as follows:</li>
<ol style="list-style-type: decimal">
<li>In Xcode, select Window &gt; Organizer
<li>Select &#8220;Provisioning Profiles&#8221; category on the left
<li>Ctrl-click an existing profile &gt; Reveal in Finder
<li>Drag and drop the new profile into the profile Finder window</li>
</ol>
</ol>
<h3>3. Update Xcode Settings</h3>
<p>After the profile is installed in Xcode, you need to make a couple edits to the project to use the provisioning profile:</p>
<ol>
<li>Edit your project&#8217;s .plist file so the Bundle ID entry matches the App ID. Ignore the alphanumeric sequence at the beginning of the ID. For instance, if your App ID is &#8220;7DW89RZKLY.com.runmonster.runmonsterfree&#8221; in the Developer Portal, just enter &#8220;com.runmonster.runmonsterfree&#8221; for the Bundle ID.</li>
<li>Edit your project&#8217;s target info to use the new provisioning profile:
<ol style="list-style-type: decimal">
<li>Select Project &gt; Edit Active Target
<li>Select the &#8220;Build&#8221; tab at the top
<li>Select the configuration you want (usually Debug)
<li>Select your new provisioning profile for the row labeled <strong>Code Signing Identity</strong>
<li>Select your new provisioning profile for the row directly underneath the <strong>Code Signing Identity</strong> row (probably labeled <strong>Any iPhone OS Device</strong>)
        </ol>
</ol>
<h3>4. Add your Application</h3>
<p>If your application is already available on the App Store, you can skip this step.</p>
<p>Before you can add a product in iTunes Connect, you must add the application the product is for. Don&#8217;t worry if you aren&#8217;t a 100% done with your app. You can still submit your app wtih stub data and add the real details later.</p>
<p><em>NOTE: Only the SKU and version fields are permanent and cannot be changed later.</em></p>
<ol>
<li>Navigate to <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a>, and log in</li>
<li>Follow the right-hand link to iTunes Connect
<ul>
<li><em>NOTE: you MUST be logged in to developer.apple.com first, or bad things will happen</em></li>
</ul>
<li>On the iTunes Connect homepage, click the &#8220;Manage Your Applications&#8221; link</li>
<li>In the top-right corner, click &#8220;Create New Application&#8221;</li>
<li>Fill out all the requested information for your app. When asked for your application binary, check the box indicating you will upload it later.</li>
</ol>
<h3>5. Add the App Binary</h3>
<p>This detail is not mentioned anywhere in Apple&#8217;s documentation, but is a requirement nonetheless. You <em>must</em> submit a binary for your application in order to successfully test in-app purchases. Even if you aren&#8217;t 100% done, you need to submit <em>a</em> binary. However, you can immediately reject the binary so it won&#8217;t go through the review process.</p>
<p>This was the crucial step I missed that caused me hours of grief and frustration. Follow these steps to add the binary:</p>
<ol>
<li>Build your application for App Store distribution</li>
<ul>
<li>If you don&#8217;t know how, navigate to the iPhone Developer Portal, click on the Distribution tab on the left, and make sure you are in the &#8220;Prepare App&#8221; tab. Now, follow the instructions in the following blue links:</li>
<ul>
<li>Obtaining your iPhone Distribution Certificate</li>
<li>Create and download your iPhone Distribution Provisioning Profile for App Store Distribution</li>
<li>Building your Application with Xcode for Distribution</li>
</ul>
</ul>
<li value="2">Navigate to your app&#8217;s page in iTunes Connect</li>
<li>Select &#8220;Upload Binary&#8221;</li>
<li>Upload your .zip compressed application</li>
<li>If you aren&#8217;t 100% ready for your app to be reviewed, then click the &#8220;Reject Binary&#8221; link on your app homepage in iTunes Connect. The app&#8217;s status should update to &#8220;Developer Rejected&#8221;.</li>
</ol>
<p>Have no fear. Apple will <em>not</em> review this version of the app once it is &#8220;Developer Rejected&#8221;. You can submit a new version of your app at any point, and having the status &#8220;Developer Rejected&#8221; doesn&#8217;t affect the wait time for your future submissions in the slightest.</p>
<h3>6. Add the Product</h3>
<p>After all that setup, we are finally ready to add the product itself to iTunes Connect.</p>
<ol>
<li>Make sure you are logged in to <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a></li>
<li>Navigate to the iTunes Connect homepage</li>
<li>Click the &#8220;Manage Your in App Purchases&#8221; link</li>
<li>Click &#8220;Create New&#8221;</li>
<li>Select your application</li>
<li>Fill in the production information:</li>
<ul>
<li><strong>Reference Name</strong>: common name for the product. I used &#8220;Pro Upgrade&#8221;. This name is non-editable, and it will not be displayed in the App Store.
<li><strong>Product ID</strong>: unique id for your app. Typically of the form <em>com.company.appname.product</em>, but it can be whatever you want. It does <em>not</em> need to have your app&#8217;s App ID as a prefix.</li>
<li><strong>Type</strong>: You have 3 choices:
<ul>
<li><strong>Non-consumable</strong>: only pay once (use this if you want a free-to-pro-upgrade product)</li>
<li><strong>Consumable</strong>: pay for every download</li>
<li><strong>Subscription</strong>: recurring payment</li>
</ul>
<li><strong>Price Tier</strong>: price of the product. See the price matrix for the different tiers.</li>
<li><strong>Cleared for Sale</strong>: check this <em>now</em>. If you don&#8217;t, you will get back an invalid product ID during testing.</li>
<li><strong>Language to Add</strong>:  Pick one. The following two fields will appear:</li>
<ul>
<li><strong>Displayed Name</strong>: Name of your product shown to your user. I chose &#8220;Upgrade to Pro&#8221;.</li>
<li><strong>Description</strong>: What the product does. The text you enter here is sent along with the Displayed Name and Price when you fetch an <code>SKProduct</code> in code.</li>
</ul>
<li><strong>Screenshot</strong>: Your feature in action. Despite the text on the screen about the screenshot submission triggering the product review process (a very sloppy design choice, IMHO), you can safely add the screenshot now <em>without</em> the product being submitted for review. After saving the product, just choose the &#8220;Submit with app binary&#8221; option. This will tie the product to the app binary, so when you finally submit the 100% complete app binary, the product will be submitted as well.</li>
</ul>
<li value="7">Click &#8220;Save&#8221;</li>
</ol>
<h3>7. Write Code</h3>
<p>Now, we finally write the code the fetches the product information we just added in iTunes Connect. To access the product data, we need to use the <code>StoreKit</code> framework.</p>
<p><em>NOTE: StoreKit does not work on the Simulator. You must test on a physical device.</em></p>
<ol>
<li>Add the <code>StoreKit</code> framework to your project.</li>
<li>Add a reference to a <code>SKProduct</code> to your .h file:</li>
</ol>
<div class="code">
<span class="comment">// InAppPurchaseManager.h</span> </p>
<p><span class="preprocessor">#import <span class="string">&lt;StoreKit/StoreKit.h&gt;</span></span> </p>
<p><span class="preprocessor">#define kInAppPurchaseManagerProductsFetchedNotification <span class="string">@&quot;kInAppPurchaseManagerProductsFetchedNotification&quot;</span></span> </p>
<p><span class="directive">@interface</span> InAppPurchaseManager : NSObject &lt;SKProductsRequestDelegate&gt;<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">SKProduct</span> *<span class="ivar">proUpgradeProduct</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">SKProductsRequest</span> *<span class="ivar">productsRequest</span>;<br />
}
</div>
<p><em>NOTE: InAppPurchaseManager is a singleton class that handles every in app purchase for our app. It&#8217;s used throughout this post as an example implementation.</em></p>
<ol>
<li value="3">Request the product, and implement the delegate protocol in the corresponding .m file:</li>
</ol>
<div class="code">
<span class="comment">// InAppPurchaseManager.m</span> </p>
<p>- (<span class="primitive">void</span>)requestProUpgradeProductData<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSSet</span> *<span class="var">productIdentifiers</span> = [<span class="built_in_type">NSSet</span> <span class="method_call">setWithObject</span>:<span class="string">@&quot;com.runmonster.runmonsterfree.upgradetopro&quot;</span> ];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">productsRequest</span> = [[<span class="built_in_type">SKProductsRequest</span> <span class="method_call">alloc</span>] <span class="method_call">initWithProductIdentifiers</span>:<span class="var">productIdentifiers</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">productsRequest</span>.<span class="property_call">delegate</span> = <span class="keyword">self</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">productsRequest</span> <span class="method_call">start</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// we will release the request object in the delegate callback</span><br />
} </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark SKProductsRequestDelegate methods</span> </p>
<p>- (<span class="primitive">void</span>)productsRequest:(<span class="custom_type">SKProductsRequest</span> *)request didReceiveResponse:(<span class="custom_type">SKProductsResponse</span> *)response<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSArray</span> *<span class="var">products</span> = <span class="var">response</span>.<span class="property_call">products</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ivar">proUpgradeProduct</span> = [<span class="var">products</span> <span class="method_call">count</span>] == <span class="number">1</span> ? [[<span class="var">products</span> <span class="method_call">firstObject</span>] <span class="method_call">retain</span>] : <span class="keyword">nil</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="ivar">proUpgradeProduct</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSLog</span>(<span class="string">@&quot;Product title: %@&quot;</span> , <span class="ivar">proUpgradeProduct</span>.<span class="property_call">localizedTitle</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSLog</span>(<span class="string">@&quot;Product description: %@&quot;</span> , <span class="ivar">proUpgradeProduct</span>.<span class="property_call">localizedDescription</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSLog</span>(<span class="string">@&quot;Product price: %@&quot;</span> , <span class="ivar">proUpgradeProduct</span>.<span class="property_call">price</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSLog</span>(<span class="string">@&quot;Product id: %@&quot;</span> , <span class="ivar">proUpgradeProduct</span>.<span class="property_call">productIdentifier</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="built_in_type">NSString</span> *<span class="var">invalidProductId</span> <span class="keyword">in</span> <span class="var">response</span>.<span class="property_call">invalidProductIdentifiers</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSLog</span>(<span class="string">@&quot;Invalid product id: %@&quot;</span> , <span class="var">invalidProductId</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// finally release the reqest we alloc/init’ed in requestProUpgradeProductData</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">productsRequest</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSNotificationCenter</span> <span class="method_call">defaultCenter</span>] <span class="method_call">postNotificationName</span>:<span class="constant">kInAppPurchaseManagerProductsFetchedNotification</span> <span class="method_call">object</span>:<span class="keyword">self</span> <span class="method_call">userInfo</span>:<span class="keyword">nil</span>];<br />
}
</div>
<p>A couple notes about the code above:</p>
<ul>
<li>When specifying the product identifier, you must use the <em>full</em> product id. For instance, &#8220;com.runmonster.runmonsterfree.upgradetopro&#8221; is used above. &#8220;upgradetopro&#8221; alone will not work.</li>
<li>If <code>response.products</code> is nil in <code>productsRequest:didReceiveResponse:</code> and your product id shows up in the <code>response.invalidProductIdentifers</code> array, then prepare yourself mentally for a wild goose chase. The <code>StoreKit</code> API offers no help, no indication as to <em>why</em> your identifier was invalid, just that it is. Lovely, isn&#8217;t it?</li>
<li>The <code>SKProduct</code> class conveniently offers localized versions of your app title and description, but not price. To handle this omission, here&#8217;s a category that will provide a localized price string for the product as well:</li>
</ul>
<div class="code">
<span class="comment">// SKProduct+LocalizedPrice.h</span> </p>
<p><span class="preprocessor">#import <span class="string">&lt;Foundation/Foundation.h&gt;</span></span><br />
<span class="preprocessor">#import <span class="string">&lt;StoreKit/StoreKit.h&gt;</span></span> </p>
<p><span class="directive">@interface</span> SKProduct (LocalizedPrice) </p>
<p><span class="property">@property</span> (<span class="attribute">nonatomic</span>, <span class="attribute">readonly</span>) <span class="built_in_type">NSString</span> *<span class="var">localizedPrice</span>;</p>
<p><span class="directive">@end</span>
</div>
<div class="code">
<span class="comment">// SKProduct+LocalizedPrice.m</span> </p>
<p><span class="preprocessor">#import <span class="string">&quot;SKProduct+LocalizedPrice.h&quot;</span></span> </p>
<p><span class="directive">@implementation</span> SKProduct (LocalizedPrice) </p>
<p>- (<span class="built_in_type">NSString</span> *)localizedPrice<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSNumberFormatter</span> *<span class="var">numberFormatter</span> = [[<span class="built_in_type">NSNumberFormatter</span> <span class="method_call">alloc</span>] <span class="method_call">init</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">numberFormatter</span> <span class="method_call">setFormatterBehavior</span>:<span class="built_in_type">NSNumberFormatterBehavior10_4</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">numberFormatter</span> <span class="method_call">setNumberStyle</span>:<span class="built_in_type">NSNumberFormatterCurrencyStyle</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">numberFormatter</span> <span class="method_call">setLocale</span>:<span class="keyword">self</span>.<span class="property_call">priceLocale</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSString</span> *<span class="var">formattedString</span> = [<span class="var">numberFormatter</span> <span class="method_call">stringFromNumber</span>:<span class="keyword">self</span>.<span class="property_call">price</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="var">numberFormatter</span> <span class="method_call">release</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="var">formattedString</span>;<br />
} </p>
<p><span class="directive">@end</span> </p>
</div>
<p>After adding all the code above, give it a shot. You should see the product information gloriously regurgitated in your console window. However, you are more than likely getting back an invalid product ID. My <a href="http://troybrant.net/blog/2010/01/invalid-product-ids/">next post</a> addresses exactly how to go about debugging this problem, but the very next section may in fact hold your solution.</p>
<h3>8. Wait a Few Hours</h3>
<p>Have you followed all the steps above to the letter, and your product is still reported as invalid? Have you painstakingly double, triple, quadruple-checked to make sure you have followed every step? Have you despaired from finding frighteningly little in-app purchase information on the web?</p>
<p>Then, you may just need to wait.</p>
<p>It takes a while for the product you added to iTunes Connect to permeate Apple&#8217;s distributed in-app sandbox environment. For me, I gave up in despair after the umpteenth time my product came back as invalid. 24 hours later, I hadn&#8217;t changed a line a code, but my IDs were coming back valid. I think it really only took a few hours for the product to propagate through Apple&#8217;s distributed network, but if you can afford to wait, you may want to give it 24 hours like I did.</p>
<h2>Purchase a Product</h2>
<p>At this point, you should be able to successfully fetch an <code>SKProduct</code> description for your product. Adding support for purchasing the product is relatively simple compared to getting the description. There are only three steps required:</p>
<ol>
<li>Write code for supporting transactions</li>
<li>Add an in app test user in iTunes Connect</li>
<li>Sign out of your iTunes Store account on your phone</li>
<li>Test the purchase</li>
</ol>
<p>We&#8217;ll start off by taking a look at the code required to support transactions.</p>
<h3>1. Write Code for Supporting Transactions</h3>
<p>First, a word of warning: <em>you</em> are responsible for developing the user interface for purchasing your product. <code>StoreKit</code> offers absolutely zero interface elements. If you want your purchase view to look like the App Store&#8217;s, well, you have to build it yourself.</p>
<p>All the code below is for the <em>backend</em> of the transaction process. It is a single class with a simple API that an outside class (like a view controller) can call to make the purchase. I recommend a similar approach if you are figuring out how best to integrate in app purchases in your app.</p>
<p>First, you need to conform to the <code>SKPaymentTransactionObserver</code> protocol:</p>
<div class="code">
<span class="comment">// InAppPurchaseManager.h</span> </p>
<p><span class="comment">// add a couple notifications sent out when the transaction completes</span><br />
<span class="preprocessor">#define kInAppPurchaseManagerTransactionFailedNotification <span class="string">@&quot;kInAppPurchaseManagerTransactionFailedNotification&quot;</span></span><br />
<span class="preprocessor">#define kInAppPurchaseManagerTransactionSucceededNotification <span class="string">@&quot;kInAppPurchaseManagerTransactionSucceededNotification&quot;</span></span> </p>
<p>&#8230;</p>
<p><span class="directive">@interface</span> InAppPurchaseManager : NSObject &lt;SKProductsRequestDelegate, SKPaymentTransactionObserver&gt;<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&#8230;<br />
} </p>
<p><span class="comment">// public methods</span><br />
- (<span class="primitive">void</span>)loadStore;<br />
- (<span class="primitive">BOOL</span>)canMakePurchases;<br />
- (<span class="primitive">void</span>)purchaseProUpgrade;</p>
<p><span class="directive">@end</span>
</div>
<p>Above, we have defined two more notifications that will be sent out with the result of the purchase transaction. For the sake of this example, we are using the <code>InAppPurchaseManager</code> class again, just as we did when when fetching the product description.</p>
<div class="code">
<span class="comment">// InAppPurchaseManager.m</span> </p>
<p><span class="preprocessor">#define kInAppPurchaseProUpgradeProductId <span class="string">@&quot;com.runmonster.runmonsterfree.upgradetopro&quot;</span></span> </p>
<p>&#8230;</p>
<p><span class="preprocessor">#pragma -</span><br />
<span class="preprocessor">#pragma Public methods</span> </p>
<p><span class="comment">//</span><br />
<span class="comment">// call this method once on startup</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)loadStore<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// restarts any purchases if they were interrupted last time the app was open</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">SKPaymentQueue</span> <span class="method_call">defaultQueue</span>] <span class="method_call">addTransactionObserver</span>:<span class="keyword">self</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// get the product description (defined in early sections)</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">requestProUpgradeProductData</span>];<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// call this before making a purchase</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">BOOL</span>)canMakePurchases<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> [<span class="built_in_type">SKPaymentQueue</span> <span class="method_call">canMakePayments</span>];<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// kick off the upgrade transaction</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)purchaseProUpgrade<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">SKPayment</span> *<span class="var">payment</span> = [<span class="built_in_type">SKPayment</span> <span class="method_call">paymentWithProductIdentifier</span>:<span class="constant">kInAppPurchaseProUpgradeProductId</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">SKPaymentQueue</span> <span class="method_call">defaultQueue</span>] <span class="method_call">addPayment</span>:<span class="var">payment</span>];<br />
} </p>
<p><span class="preprocessor">#pragma -</span><br />
<span class="preprocessor">#pragma Purchase helpers</span> </p>
<p><span class="comment">//</span><br />
<span class="comment">// saves a record of the transaction by storing the receipt to disk</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)recordTransaction:(<span class="built_in_type">SKPaymentTransaction</span> *)transaction<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> ([<span class="var">transaction</span>.<span class="property_call">payment</span>.<span class="property_call">productIdentifier</span> <span class="method_call">isEqualToString</span>:<span class="constant">kInAppPurchaseProUpgradeProductId</span>])<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// save the transaction receipt to disk</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSUserDefaults</span> <span class="method_call">standardUserDefaults</span>] <span class="method_call">setValue</span>:<span class="var">transaction</span>.<span class="property_call">transactionReceipt</span> <span class="method_call">forKey</span>:<span class="string">@&quot;proUpgradeTransactionReceipt&quot;</span> ];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSUserDefaults</span> <span class="method_call">standardUserDefaults</span>] <span class="method_call">synchronize</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// enable pro features</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)provideContent:(<span class="built_in_type">NSString</span> *)productId<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> ([<span class="var">productId</span> <span class="method_call">isEqualToString</span>:<span class="constant">kInAppPurchaseProUpgradeProductId</span>])<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// enable the pro features</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSUserDefaults</span> <span class="method_call">standardUserDefaults</span>] <span class="method_call">setBool</span>:<span class="keyword">YES</span> <span class="method_call">forKey</span>:<span class="string">@&quot;isProUpgradePurchased&quot;</span> ];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSUserDefaults</span> <span class="method_call">standardUserDefaults</span>] <span class="method_call">synchronize</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// removes the transaction from the queue and posts a notification with the transaction result</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)finishTransaction:(<span class="built_in_type">SKPaymentTransaction</span> *)transaction wasSuccessful:(<span class="primitive">BOOL</span>)wasSuccessful<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// remove the transaction from the payment queue.</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">SKPaymentQueue</span> <span class="method_call">defaultQueue</span>] <span class="method_call">finishTransaction</span>:<span class="var">transaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSDictionary</span> *<span class="var">userInfo</span> = [<span class="built_in_type">NSDictionary</span> <span class="method_call">dictionaryWithObjectsAndKeys</span>:<span class="var">transaction</span>, <span class="string">@&quot;transaction&quot;</span> , <span class="keyword">nil</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">wasSuccessful</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// send out a notification that we&#8217;ve finished the transaction</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSNotificationCenter</span> <span class="method_call">defaultCenter</span>] <span class="method_call">postNotificationName</span>:<span class="constant">kInAppPurchaseManagerTransactionSucceededNotification</span> <span class="method_call">object</span>:<span class="keyword">self</span> <span class="method_call">userInfo</span>:<span class="var">userInfo</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// send out a notification for the failed transaction</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">NSNotificationCenter</span> <span class="method_call">defaultCenter</span>] <span class="method_call">postNotificationName</span>:<span class="constant">kInAppPurchaseManagerTransactionFailedNotification</span> <span class="method_call">object</span>:<span class="keyword">self</span> <span class="method_call">userInfo</span>:<span class="var">userInfo</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// called when the transaction was successful</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)completeTransaction:(<span class="built_in_type">SKPaymentTransaction</span> *)transaction<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">recordTransaction</span>:<span class="var">transaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">provideContent</span>:<span class="var">transaction</span>.<span class="property_call">payment</span>.<span class="property_call">productIdentifier</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">finishTransaction</span>:<span class="var">transaction</span> <span class="method_call">wasSuccessful</span>:<span class="keyword">YES</span>];<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// called when a transaction has been restored and and successfully completed</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)restoreTransaction:(<span class="built_in_type">SKPaymentTransaction</span> *)transaction<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">recordTransaction</span>:<span class="var">transaction</span>.<span class="property_call">originalTransaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">provideContent</span>:<span class="var">transaction</span>.<span class="property_call">originalTransaction</span>.<span class="property_call">payment</span>.<span class="property_call">productIdentifier</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">finishTransaction</span>:<span class="var">transaction</span> <span class="method_call">wasSuccessful</span>:<span class="keyword">YES</span>];<br />
} </p>
<p><span class="comment">//</span><br />
<span class="comment">// called when a transaction has failed</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)failedTransaction:(<span class="built_in_type">SKPaymentTransaction</span> *)transaction<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="var">transaction</span>.<span class="property_call">error</span>.<span class="property_call">code</span> != <span class="built_in_type">SKErrorPaymentCancelled</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// error!</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">finishTransaction</span>:<span class="var">transaction</span> <span class="method_call">wasSuccessful</span>:<span class="keyword">NO</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// this is fine, the user just cancelled, so don&#8217;t notify</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[<span class="built_in_type">SKPaymentQueue</span> <span class="method_call">defaultQueue</span>] <span class="method_call">finishTransaction</span>:<span class="var">transaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
} </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark SKPaymentTransactionObserver methods</span> </p>
<p><span class="comment">//</span><br />
<span class="comment">// called when the transaction status is updated</span><br />
<span class="comment">//</span><br />
- (<span class="primitive">void</span>)paymentQueue:(<span class="built_in_type">SKPaymentQueue</span> *)queue updatedTransactions:(<span class="built_in_type">NSArray</span> *)transactions<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="built_in_type">SKPaymentTransaction</span> *<span class="var">transaction</span> <span class="keyword">in</span> <span class="var">transactions</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">switch</span> (<span class="var">transaction</span>.<span class="property_call">transactionState</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="built_in_type">SKPaymentTransactionStatePurchased</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">completeTransaction</span>:<span class="var">transaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="built_in_type">SKPaymentTransactionStateFailed</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">failedTransaction</span>:<span class="var">transaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="built_in_type">SKPaymentTransactionStateRestored</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">restoreTransaction</span>:<span class="var">transaction</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">default</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
} </p>
</div>
<p>In order to test this jumble of new code, you will need to write the code that calls the <code>loadStore</code>, <code>canMakePurchases</code>, and <code>purchaseProUpgrade</code> methods as well.</p>
<p>As you can see, there&#8217;s a good bit of code required to support transactions. For a full explanation of the code, see the official In App Purchase Programming Guide &#8211; <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/AddingaStoretoYourApplication/AddingaStoretoYourApplication.html#//apple_ref/doc/uid/TP40008267-CH101-SW1">http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/AddingaStoretoYourApplication/AddingaStoretoYourApplication.html#//apple_ref/doc/uid/TP40008267-CH101-SW1</a>.</p>
<p>The code above has a few parts that are specific to my implementation. Most notably, in <code>provideContent:</code>, the <code>@"isProUpgradePurchased"</code> BOOL field of <code>NSUserDefaults</code> is set to YES. All throughout the rest of the application, this BOOL is checked to determine whether or not to enable the pro features. If you are also implementing a free to pro upgrade product, I recommend using the same approach.</p>
<h3>2. Add a Test User</h3>
<p>In order to try out the mess of code you just added to your project, you will need to create a user in iTunes Connect for testing in app purchases. You can use this test account to purchase a product without being charged by Apple.</p>
<p>To create a test user, follow these steps:</p>
<ol>
<li>Log in to <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a></li>
<li>Navigate to iTunes Connect</li>
<li>Select &#8220;Manage Users&#8221; on the iTunes Conect home page</li>
<li>Select &#8220;In App Purchase Test User&#8221;</li>
<li>Select &#8220;Add New User&#8221;</li>
<li>Fill out the user information. None of the information needs to be legit. I recommend a short, fake email address and a short password since you will need to type it in your phone during testing.</li>
<li>Select &#8220;Save&#8221;</li>
</ol>
<p>You will enter the email and password for this user on the iPhone during your testing.</p>
<h3>3. Sign Out On Your Device</h3>
<p>Before you can start testing in app purchases, you must sign out of the iTunes Store on your device. To sign out, follow these steps:</p>
<ol>
<li>Open the Settings App</li>
<li>Tap the &#8220;Store&#8221; row</li>
<li>Tap &#8220;Sign Out&#8221;</li>
</ol>
<h3>4. Test the Purchase</h3>
<p>Now, you are finally ready to try out an in app purchase. Testing is simple:</p>
<ol>
<li>Run your app on your device</li>
<li>Trigger the purchase</li>
<li>When prompted for username and password, enter your test user details</li>
</ol>
<p>If you repeat the purchase with the same account, you will be notified that you have already made the purchase. This is fine, just click &#8220;Yes&#8221; when prompted if you want to download the product again.</p>
<h2>That&#8217;s a Wrap</h2>
<p>Getting in app purchases to work is a lot more painful than it should be. It took several days of blood, sweat, and tears to get it working in my own application, and hopefully this post has helped short circuit that cycle of pain and suffering for you as well.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/feed/</wfw:commentRss>
		<slash:comments>133</slash:comments>
		</item>
	</channel>
</rss>

