Julien's Bloghttps://blog.caffeine.lu/2013-01-29T00:00:00+01:00Migrating a local store to iCloud in iOS 6.12013-01-29T00:00:00+01:00Julien Poissonniertag:blog.caffeine.lu,2013-01-29:migrating-a-local-store-to-icloud-in-ios-61.html<p>As discussed in a <a href="https://blog.caffeine.lu/migrating-an-icloud-coredata-store.html">previous post</a> there&#8217;s a bug on iOS 6.0 when migrating a persistent store from iCloud storage to local storage. iOS 6.1 seems to fix this, but introduces a new bug when migrating in the opposite direction, from local to iCloud. Apple notes this in the <a href="https://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_1/index.html#//apple_ref/doc/uid/TP40012869">release notes</a>:</p> <pre class="wrap">Known Issues Moving data from a local sandbox to iCloud using migratePersistentStore causes a crash. Instead, manually migrate the data store by iterating over the objects in the local data store file.</pre> <p>I outlined the basic procedure to perform such a manual migration previously, but I recommend you use Drew McCormack&#8217;s <a href="https://github.com/drewmccormack/iCloudCoreDataTester/blob/master/iCloudCoreDataTester/MCPersistentStoreMigrator.h"><code>MCPersistentStoreMigrator</code></a> class found in his <a href="https://github.com/drewmccormack/iCloudCoreDataTester">iCloudCoreDataTester</a> github repository<sup id="fnref:mccormack_blog"><a class="footnote-ref" href="#fn:mccormack_blog" rel="footnote">1</a></sup>. This class uses your model&#8217;s <code>NSEntityDescription</code>s to implement generic methods to perform a manual migration.</p> <p>Here&#8217;s an example on how you can use <code>MCPersistentStoreMigrator</code>:</p> <div class="codehilite"><pre><span></span><span class="cm">/* Load the iCloud store, optionally migrate fallback store to iCloud */</span> <span class="p">-</span> <span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nf">loadiCloudStoreMigrate:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">migrate</span> <span class="nf">error:</span><span class="p">(</span><span class="bp">NSError</span> <span class="o">*</span> <span class="k">__autoreleasing</span> <span class="o">*</span><span class="p">)</span><span class="nv">error</span> <span class="p">{</span> <span class="kt">BOOL</span> <span class="n">success</span> <span class="o">=</span> <span class="nb">YES</span><span class="p">;</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">localError</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span> <span class="bp">NSFileManager</span> <span class="o">*</span><span class="n">fm</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSFileManager</span> <span class="n">defaultManager</span><span class="p">];</span> <span class="bp">NSURL</span> <span class="o">*</span><span class="n">iCloudDataURL</span> <span class="o">=</span> <span class="p">[</span><span class="n">fm</span> <span class="nl">URLForUbiquityContainerIdentifier</span><span class="p">:</span><span class="nb">nil</span><span class="p">];</span> <span class="n">iCloudDataURL</span> <span class="o">=</span> <span class="p">[</span><span class="n">iCloudDataURL</span> <span class="nl">URLByAppendingPathComponent</span><span class="p">:</span><span class="n">kTransactionLogsDirectory</span><span class="p">];</span> <span class="bp">NSDictionary</span> <span class="o">*</span><span class="n">options</span> <span class="o">=</span> <span class="l">@{</span> <span class="nl">NSPersistentStoreUbiquitousContentNameKey</span> <span class="p">:</span> <span class="n">kiCloudContentName</span><span class="p">,</span> <span class="nl">NSPersistentStoreUbiquitousContentURLKey</span> <span class="p">:</span> <span class="n">iCloudDataURL</span><span class="p">,</span> <span class="nl">NSMigratePersistentStoresAutomaticallyOption</span><span class="p">:</span> <span class="m">@YES</span><span class="p">,</span> <span class="nl">NSInferMappingModelAutomaticallyOption</span><span class="p">:</span> <span class="m">@YES</span> <span class="l">}</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">migrate</span><span class="p">)</span> <span class="p">{</span> <span class="bp">NSManagedObjectModel</span> <span class="o">*</span><span class="n">model</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSManagedObjectModel</span> <span class="nl">mergedModelFromBundles</span><span class="p">:</span><span class="nb">nil</span><span class="p">];</span> <span class="n">MCPersistentStoreMigrator</span> <span class="o">*</span><span class="n">migrator</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MCPersistentStoreMigrator</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithManagedObjectModel</span><span class="p">:</span><span class="n">model</span> <span class="nl">sourceStoreURL</span><span class="p">:</span><span class="nb">self</span><span class="p">.</span><span class="n">fallbackStoreURL</span> <span class="nl">destinationStoreURL</span><span class="p">:</span><span class="nb">self</span><span class="p">.</span><span class="n">iCloudStoreURL</span><span class="p">];</span> <span class="n">migrator</span><span class="p">.</span><span class="n">destinationStoreOptions</span> <span class="o">=</span> <span class="n">options</span><span class="p">;</span> <span class="p">[</span><span class="n">migrator</span> <span class="n">beginMigration</span><span class="p">];</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">migrateError</span><span class="p">;</span> <span class="o">/*</span> <span class="n">call</span> <span class="nl">migrateEntityWithName</span><span class="p">:</span><span class="nl">batchSize</span><span class="p">:</span><span class="nl">save</span><span class="p">:</span><span class="n">error</span> <span class="k">for</span> <span class="n">each</span> <span class="n">of</span> <span class="n">your</span> <span class="n">entities</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">migrator</span> <span class="nl">migrateEntityWithName</span><span class="p">:</span><span class="s">@&quot;SomeEntity&quot;</span> <span class="nl">batchSize</span><span class="p">:</span><span class="mi">500</span> <span class="nl">save</span><span class="p">:</span><span class="nb">YES</span> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">localError</span><span class="p">])</span> <span class="p">{</span> <span class="n">NSLog</span><span class="p">(</span><span class="s">@&quot;Error migrating SomeEntity entities to iCloud: %@&quot;</span><span class="p">,</span> <span class="n">migrateError</span><span class="p">);</span> <span class="n">success</span> <span class="o">=</span> <span class="nb">NO</span><span class="p">;</span> <span class="p">}</span> <span class="p">[</span><span class="n">migrator</span> <span class="n">endMigration</span><span class="p">];</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">persistentStoreCoordinator</span> <span class="n">lock</span><span class="p">];</span> <span class="nb">self</span><span class="p">.</span><span class="n">iCloudStore</span> <span class="o">=</span> <span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">persistentStoreCoordinator</span> <span class="nl">addPersistentStoreWithType</span><span class="p">:</span><span class="n">NSSQLiteStoreType</span> <span class="nl">configuration</span><span class="p">:</span><span class="nb">nil</span> <span class="nl">URL</span><span class="p">:</span><span class="nb">self</span><span class="p">.</span><span class="n">iCloudStoreURL</span> <span class="nl">options</span><span class="p">:</span><span class="n">options</span> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">localError</span><span class="p">];</span> <span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">persistentStoreCoordinator</span> <span class="n">unlock</span><span class="p">];</span> <span class="n">success</span> <span class="o">=</span> <span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">iCloudStore</span> <span class="o">!=</span> <span class="nb">nil</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">localError</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">error</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">))</span> <span class="p">{</span> <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="n">localError</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="n">success</span><span class="p">;</span> <span class="p">}</span> </pre></div> <div class="footnote"> <hr /> <ol> <li id="fn:mccormack_blog"> <p>You should also take a look at his series of blog posts about iCloud and Core Data from <a href="http://mentalfaculty.tumblr.com/archive">May/June 2012</a>.&#160;<a class="footnote-backref" href="#fnref:mccormack_blog" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p> </li> </ol> </div>Creating a Tilt-shift Effect with CoreImage2012-12-18T00:00:00+01:00Julien Poissonniertag:blog.caffeine.lu,2012-12-18:creating-a-tilt-shift-effect-with-coreimage.html<h1>Tilt-shift</h1> <p>A tilt-shift effect can be used to simulate a miniature scene:</p> <p><img alt="Tile-shift" src="static/images/tilt.jpg" /></p> <h1>QuartzComposer</h1> <p>Using QuartzComposer<sup id="fnref:qtz"><a class="footnote-ref" href="#fn:qtz" rel="footnote">1</a></sup> we can build a filter chain to apply this effect and play around with it.</p> <p>As described in the <a href="https://developer.apple.com/library/ios/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_filer_recipes/ci_filter_recipes.html#//apple_ref/doc/uid/TP30001185-CH4-SW17">CoreImage Documentation</a> the first step is to setup a blur filter. Since the blur filter makes the image slightly larger, we need to crop it back to the original size.</p> <p>The gradient mask is built by combining two linear gradients and is then used to blend the blurred image with the original image. Note that we need to crop the gradients to the correct size since the output image from CIGradientFilter has infinite size.</p> <p><a href="http://blog.caffeine.lu/static/uploads/Tilt.qtz.zip">Download the QuartzComposer composition</a> </p> <p><img alt="QuartzComposer" src="static/images/qtz.png" /></p> <h1>CoreImage</h1> <p>This same filter chain can be implemented using CoreImage:</p> <div class="codehilite"><pre><span></span><span class="p">-</span> <span class="p">(</span><span class="bp">CIImage</span> <span class="o">*</span><span class="p">)</span><span class="nf">outputImage</span> <span class="p">{</span> <span class="bp">CGRect</span> <span class="n">cropRect</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="n">inputImage</span><span class="p">.</span><span class="n">extent</span><span class="p">;</span> <span class="n">CGFloat</span> <span class="n">height</span> <span class="o">=</span> <span class="n">cropRect</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">;</span> <span class="bp">CIFilter</span> <span class="o">*</span><span class="n">blur</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CIGaussianBlur&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputImage&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="p">.</span><span class="n">inputImage</span><span class="p">,</span> <span class="s">@&quot;inputRadius&quot;</span><span class="p">,</span> <span class="l">@(</span><span class="nb">self</span><span class="p">.</span><span class="n">inputRadius</span><span class="l">)</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span> <span class="n">blur</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CICrop&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputImage&quot;</span><span class="p">,</span> <span class="n">blur</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="s">@&quot;inputRectangle&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithCGRect</span><span class="p">:</span><span class="n">cropRect</span><span class="p">],</span> <span class="nb">nil</span><span class="p">];</span> <span class="bp">CIFilter</span> <span class="o">*</span><span class="n">topGradient</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CILinearGradient&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputPoint0&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithX</span><span class="p">:</span><span class="mi">0</span> <span class="nl">Y</span><span class="p">:(</span><span class="nb">self</span><span class="p">.</span><span class="n">inputTop</span> <span class="o">*</span> <span class="n">height</span><span class="p">)],</span> <span class="s">@&quot;inputColor0&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIColor</span> <span class="nl">colorWithRed</span><span class="p">:</span><span class="mi">0</span> <span class="nl">green</span><span class="p">:</span><span class="mi">1</span> <span class="nl">blue</span><span class="p">:</span><span class="mi">0</span> <span class="nl">alpha</span><span class="p">:</span><span class="mi">1</span><span class="p">],</span> <span class="s">@&quot;inputPoint1&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithX</span><span class="p">:</span><span class="mi">0</span> <span class="nl">Y</span><span class="p">:(</span><span class="nb">self</span><span class="p">.</span><span class="n">inputCenter</span> <span class="o">*</span> <span class="n">height</span><span class="p">)],</span> <span class="s">@&quot;inputColor1&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIColor</span> <span class="nl">colorWithRed</span><span class="p">:</span><span class="mi">0</span> <span class="nl">green</span><span class="p">:</span><span class="mi">1</span> <span class="nl">blue</span><span class="p">:</span><span class="mi">0</span> <span class="nl">alpha</span><span class="p">:</span><span class="mi">0</span><span class="p">],</span> <span class="nb">nil</span><span class="p">];</span> <span class="bp">CIFilter</span> <span class="o">*</span><span class="n">bottomGradient</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CILinearGradient&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputPoint0&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithX</span><span class="p">:</span><span class="mi">0</span> <span class="nl">Y</span><span class="p">:(</span><span class="nb">self</span><span class="p">.</span><span class="n">inputBottom</span> <span class="o">*</span> <span class="n">height</span><span class="p">)],</span> <span class="s">@&quot;inputColor0&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIColor</span> <span class="nl">colorWithRed</span><span class="p">:</span><span class="mi">0</span> <span class="nl">green</span><span class="p">:</span><span class="mi">1</span> <span class="nl">blue</span><span class="p">:</span><span class="mi">0</span> <span class="nl">alpha</span><span class="p">:</span><span class="mi">1</span><span class="p">],</span> <span class="s">@&quot;inputPoint1&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithX</span><span class="p">:</span><span class="mi">0</span> <span class="nl">Y</span><span class="p">:(</span><span class="nb">self</span><span class="p">.</span><span class="n">inputCenter</span> <span class="o">*</span> <span class="n">height</span><span class="p">)],</span> <span class="s">@&quot;inputColor1&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIColor</span> <span class="nl">colorWithRed</span><span class="p">:</span><span class="mi">0</span> <span class="nl">green</span><span class="p">:</span><span class="mi">1</span> <span class="nl">blue</span><span class="p">:</span><span class="mi">0</span> <span class="nl">alpha</span><span class="p">:</span><span class="mi">0</span><span class="p">],</span> <span class="nb">nil</span><span class="p">];</span> <span class="n">topGradient</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CICrop&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputImage&quot;</span><span class="p">,</span> <span class="n">topGradient</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="s">@&quot;inputRectangle&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithCGRect</span><span class="p">:</span><span class="n">cropRect</span><span class="p">],</span> <span class="nb">nil</span><span class="p">];</span> <span class="n">bottomGradient</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CICrop&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputImage&quot;</span><span class="p">,</span> <span class="n">bottomGradient</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="s">@&quot;inputRectangle&quot;</span><span class="p">,</span> <span class="p">[</span><span class="bp">CIVector</span> <span class="nl">vectorWithCGRect</span><span class="p">:</span><span class="n">cropRect</span><span class="p">],</span> <span class="nb">nil</span><span class="p">];</span> <span class="bp">CIFilter</span> <span class="o">*</span><span class="n">gradients</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CIAdditionCompositing&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputImage&quot;</span><span class="p">,</span> <span class="n">topGradient</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="s">@&quot;inputBackgroundImage&quot;</span><span class="p">,</span> <span class="n">bottomGradient</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span> <span class="bp">CIFilter</span> <span class="o">*</span><span class="n">tiltShift</span> <span class="o">=</span> <span class="p">[</span><span class="bp">CIFilter</span> <span class="nl">filterWithName</span><span class="p">:</span><span class="s">@&quot;CIBlendWithMask&quot;</span> <span class="nl">keysAndValues</span><span class="p">:</span><span class="s">@&quot;inputImage&quot;</span><span class="p">,</span> <span class="n">blur</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="s">@&quot;inputBackgroundImage&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="p">.</span><span class="n">inputImage</span><span class="p">,</span> <span class="s">@&quot;inputMaskImage&quot;</span><span class="p">,</span> <span class="n">gradients</span><span class="p">.</span><span class="n">outputImage</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span> <span class="k">return</span> <span class="n">tiltShift</span><span class="p">.</span><span class="n">outputImage</span><span class="p">;</span> <span class="p">}</span> </pre></div> <p>The full source code for the TiltShiftFilter can be found at <a href="https://gist.github.com/4327330">https://gist.github.com/4327330</a>.</p> <p>From here on we can easily modify this. Instead of having the gradients along the Y-axis we could pass in vectors to control the direction of the gradients and have vertical or diagonal fields in focus. Another option would be to use a <code>CIRadialGradient</code> instead of two linear gradients and have a circular field.</p> <div class="footnote"> <hr /> <ol> <li id="fn:qtz"> <p>You can find QuartzComposer in the Graphics Tools for Xcode package on <a href="https://developer.apple.com/downloads">https://developer.apple.com/downloads</a>&#160;<a class="footnote-backref" href="#fnref:qtz" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p> </li> </ol> </div>Migrating an iCloud CoreData Store2012-11-25T00:00:00+01:00Julien Poissonniertag:blog.caffeine.lu,2012-11-25:migrating-an-icloud-coredata-store.html<h1>The Problem</h1> <p>In my <a href="http://blog.caffeine.lu/problems-with-core-data-icloud-storage.html">previous post</a> I mentioned some issues with Core Data iCloud storage. One issue I didn&#8217;t mention is that on iOS 6 migrating a persistent store from iCloud to local storage using the <code>migratePersistentStore:toURL:options:withType:error:</code> method is broken (<a href="http://openradar.appspot.com/radar?id=2328401">rdar://12253540</a>). Using the method logs the following error message:</p> <pre class="wrap">2012-11-25 15:48:49.213 Migration[27675:1903] -[NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error:]( 1055): CoreData: Ubiquity: Error: A persistent store which has been previously added to a coordinator using the iCloud integration options must always be added to the coordinator with the options present in the options dictionary. If you wish to use the store without iCloud, migrate the data from the iCloud store file to a new store file in local storage. file://localhost/var/mobile/Applications/02963CFC-D52A-4005-9555-B5E2377B3ADB/Documents/local.sqlite This will be a fatal error in a future release.</pre> <p>Worse, trying to save changes to disc will now result in an error:</p> <div class="codehilite"><pre><span></span>Error Domain=NSCocoaErrorDomain Code=134030 &quot;The operation couldn’t be completed. (Cocoa error 134030.)&quot; </pre></div> <p>Migrating in the other direction from local storage to iCloud storage works fine.</p> <h1>What&#8217;s happening?</h1> <p>The error message gives us a hint about what&#8217;s going on: CoreData seems to think that the migrated store was setup with &#8220;iCloud integration options&#8221;. Inspecting the local store&#8217;s metadata reveals that there are indeed &#8220;ubiquity&#8221; options present:</p> <div class="codehilite"><pre><span></span><span class="bp">NSDictionary</span> <span class="o">*</span><span class="n">metadata</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSPersistentStoreCoordinator</span> <span class="nl">metadataForPersistentStoreOfType</span><span class="p">:</span><span class="n">NSSQLiteStoreType</span> <span class="nl">URL</span><span class="p">:</span><span class="n">localStoreURL</span> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span> <span class="n">NSLog</span><span class="p">(</span><span class="s">@&quot;metadata: %@&quot;</span><span class="p">,</span> <span class="n">metadata</span><span class="p">);</span> <span class="mi">2012</span><span class="o">-</span><span class="mi">11</span><span class="o">-</span><span class="mi">25</span> <span class="mi">15</span><span class="o">:</span><span class="mi">48</span><span class="o">:</span><span class="mf">49.225</span> <span class="n">Migration</span><span class="p">[</span><span class="mi">27675</span><span class="o">:</span><span class="mi">1903</span><span class="p">]</span> <span class="nl">metadata</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* snip */</span> <span class="s">&quot;com.apple.coredata.ubiquity.baseline.timestamp&quot;</span> <span class="o">=</span> <span class="mi">1353854739</span><span class="p">;</span> <span class="s">&quot;com.apple.coredata.ubiquity.token&quot;</span> <span class="o">=</span> <span class="o">&lt;</span><span class="mi">0</span><span class="n">a7f4e58</span> <span class="mi">4</span><span class="n">c5693df</span> <span class="mi">2081</span><span class="n">ac0a</span> <span class="n">c476bc0f</span> <span class="mi">6398</span><span class="n">af80</span><span class="o">&gt;</span><span class="p">;</span> <span class="s">&quot;com.apple.coredata.ubiquity.ubiquitized&quot;</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> </pre></div> <p>It seems that the migration method copies the iCloud store&#8217;s metadata over to the new local store, including the ubiquity options.</p> <h1>The Solution</h1> <p>There are two methods to work around this bug: you can fix the store&#8217;s metadata, or you can manually migrate your data between two CoreData stacks.</p> <h2>Fixing the Metadata</h2> <p>Fixing the metadata is pretty easy and works on iOS 6, but might fall in a gray area regarding use of unsupported API since the names of the metadata keys are an implementation detail and not publicly documented. If you use this method you should make sure to thoroughly test migration on any new iOS releases. Note that you must remove the store from the <code>NSPersistentStoreCoordinator</code> before modifying its metadata.</p> <div class="codehilite"><pre><span></span><span class="p">[</span><span class="nb">self</span> <span class="n">dropStores</span><span class="p">];</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span> <span class="kt">BOOL</span> <span class="n">success</span> <span class="o">=</span> <span class="nb">NO</span><span class="p">;</span> <span class="bp">NSMutableDictionary</span> <span class="o">*</span><span class="n">metadata</span><span class="p">;</span> <span class="n">metadata</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">NSPersistentStoreCoordinator</span> <span class="nl">metadataForPersistentStoreOfType</span><span class="p">:</span><span class="n">NSSQLiteStoreType</span> <span class="nl">URL</span><span class="p">:</span><span class="n">localStoreURL</span> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">]</span> <span class="n">mutableCopy</span><span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="n">metadata</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="n">metadata</span> <span class="nl">removeObjectForKey</span><span class="p">:</span><span class="s">@&quot;com.apple.coredata.ubiquity.baseline.timestamp&quot;</span><span class="p">];</span> <span class="p">[</span><span class="n">metadata</span> <span class="nl">removeObjectForKey</span><span class="p">:</span><span class="s">@&quot;com.apple.coredata.ubiquity.token&quot;</span><span class="p">];</span> <span class="p">[</span><span class="n">metadata</span> <span class="nl">removeObjectForKey</span><span class="p">:</span><span class="s">@&quot;com.apple.coredata.ubiquity.ubiquitized&quot;</span><span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="bp">NSPersistentStoreCoordinator</span> <span class="nl">setMetadata</span><span class="p">:</span><span class="n">metadata</span> <span class="nl">forPersistentStoreOfType</span><span class="p">:</span><span class="n">NSSQLiteStoreType</span> <span class="nl">URL</span><span class="p">:</span><span class="n">localStoreURL</span> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">])</span> <span class="p">{</span> <span class="n">success</span> <span class="o">=</span> <span class="nb">NO</span><span class="p">;</span> <span class="n">DDLogError</span><span class="p">(</span><span class="s">@&quot;Error setting metadata for migrated fallback store: %@&quot;</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">success</span> <span class="o">=</span> <span class="p">[</span><span class="nb">self</span> <span class="nl">loadLocalStore</span><span class="p">:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span> <span class="p">}</span> <span class="p">}</span> </pre></div> <h2>Manual Migration</h2> <p>Manually migrating the data requires a lot more work. You setup a second, entirely separate, CoreData stack for your local store. You then fetch the data from your iCloud stack and create a new entity in your local stack for each iCloud entity and copy all its properties. Something along the lines of:</p> <div class="codehilite"><pre><span></span><span class="bp">NSManagedObjectContext</span> <span class="o">*</span><span class="n">localContext</span> <span class="o">=</span> <span class="p">...</span> <span class="bp">NSManagedObjectContext</span> <span class="o">*</span><span class="n">iCloudContext</span> <span class="o">=</span> <span class="p">...</span> <span class="bp">NSArray</span> <span class="o">*</span><span class="n">fetchResults</span> <span class="o">=</span> <span class="p">[</span><span class="n">iCloudContext</span> <span class="nl">executeFetchRequest</span><span class="p">:</span><span class="n">request</span> <span class="nl">error</span><span class="p">:</span><span class="o">&amp;</span><span class="n">error</span><span class="p">];</span> <span class="k">for</span> <span class="p">(</span><span class="bp">NSManagedObject</span> <span class="o">*</span><span class="n">obj</span> <span class="k">in</span> <span class="n">fetchResults</span><span class="p">)</span> <span class="p">{</span> <span class="bp">NSManagedObject</span> <span class="o">*</span><span class="n">migratedObject</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSEntityDescription</span> <span class="nl">insertNewObjectForEntityForName</span><span class="p">:</span><span class="s">@&quot;MyModelClass&quot;</span> <span class="nl">inManagedObjectContext</span><span class="p">:</span><span class="n">localContext</span><span class="p">]</span> <span class="n">migratedObject</span><span class="p">.</span><span class="n">property1</span> <span class="o">=</span> <span class="n">obj</span><span class="p">.</span><span class="n">property1</span><span class="p">;</span> <span class="n">migratedObject</span><span class="p">.</span><span class="n">property2</span> <span class="o">=</span> <span class="n">obj</span><span class="p">.</span><span class="n">property2</span><span class="p">;</span> <span class="p">...</span> <span class="p">}</span> </pre></div> <p>This is fairly simple as long as the properties are discrete values, but gets a bit tricker for relationships. <a href="https://developer.apple.com/videos/wwdc/2012/?id=227">WWDC 2012 Session 227</a> explains the procedure in more detail.</p> <p>While the manual migration is more complex, it does have some advantages. First, it&#8217;s unlikely to break because of changes done to fix this bug or changes to the metadata keys. More importantly, <code>migratePersistentStore:toURL:options:withType:error</code> performs the migration in one go. It has to load all the data into memory and copies it into the new store in one action. If your database is really large this might not be possible. The manual migration method can split the data fetching into batches and thus keep the maximum memory needed lower (see Session 227).</p>Problems with Core Data iCloud Storage2012-11-24T00:00:00+01:00Julien Poissonniertag:blog.caffeine.lu,2012-11-24:problems-with-core-data-icloud-storage.html<h1>What is iCloud?</h1> <p>Apple uses iCloud as an umbrella term for all their online services and APIs, so it&#8217;s easy to get confused about what iCloud actually is. There&#8217;s iCloud e-mail, which is just IMAP. Calendars and contacts are synchronized using CalDAV and CardDav. Notes use the IMAP server of your email account. All these are completely independent of the iCloud APIs available to iOS and OS X developers.</p> <p>There are actually three different <a href="https://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloudDesignGuide/Chapters/Introduction.html#//apple_ref/doc/uid/TP40012094-CH1-SW1">iCloud APIs</a> developers can use:</p> <ul> <li> <p>Ubiquitous Key Value Store: <code>NSUbiquitousKeyValueStore</code> is a simple key-value store with an API modelled after <code>NSUserDefaults</code>. While it can sometimes be a bit slow to propagate across all your devices, this works pretty well. You use this to store discrete values, for example preferences or highscores or similar simple data.</p> </li> <li> <p>Document storage: when using document storage your app has access to a special folder called <em>ubiquity container</em>. Files stored in this container are synchronized across your devices. It&#8217;s not quite as simple as using the normal filesystem since the files might change at any moment, or even get deleted from under you. <code>UIDocument</code> handles a lot of this file coordination for you, so you&#8217;ll almost always want to use that.</p> </li> <li> <p>Core Data storage: this builds on top of document storage. Your SQLite database isn&#8217;t stored directly inside the ubiquity container<sup id="fnref:nosync"><a class="footnote-ref" href="#fn:nosync" rel="footnote">1</a></sup>, instead each time you save your <code>NSManagedObjectContext</code> transaction logs are generated that represent the changes and these logs are then stored in the ubiquity container. Other devices download and import these transaction logs, replicating all the changes.</p> </li> </ul> <h1>Ubiquity Containers and Transaction Logs</h1> <p>Document storage and Core Data storage both use the ubiquity container, but the key-value store is completely independent of this.</p> <p>You can inspect the ubiquity containers of the apps installed on your iOS or OS X devices by logging in to <a href="http://developer.icloud.com">developer.icloud.com</a> or by browsing to <code>~/Library/Mobile\ Documents</code> on your Mac.</p> <p>Drew McCormack does a great job explaining the format of the transaction logs in a <a href="http://mentalfaculty.tumblr.com/post/23231176783/under-the-sheets-with-icloud-and-core-data-how-it">blog post</a>. Note that you should never touch these logs directly. Removing transaction logs manually will break synchronization, unless you delete <em>all</em> the transaction logs <em>and</em> delete the SQLite databases on each device, basically resetting all the data.</p> <h1>Problems with Core Data iCloud Storage</h1> <p>Core Data in iCloud has a number of problems and hard to deal with use cases. These problems mostly stem from the fact that the API is presented to developers as a black box: you set up your persistent store with ubiquity options and everything else happens automatically in the background. There&#8217;s no way to hook into the underlying machinery dealing with the transaction logs, so when something goes wrong there&#8217;s not much you can do in your application.</p> <p>When the user resets the container of your app using the iCloud settings, you need to completely drop and rebuild your Core Data stack, but there is no built-in way to detect these resets. If you don&#8217;t detect and handle it properly, synchronization just stops working. Not good. The usual method to deal with detecting resets is to use the document storage API to store an additional &#8220;sentinel&#8221; file in your ubiquity container. Since the transaction logs and the sentinel file are all stored in the same ubiquity container, you know that if the sentinel file disappears the transaction logs must have been deleted too.</p> <p>You can handle the user logging out of iCloud or logging in to a different account the same way since the ubiquity container is tied to the account. Alternatively, on iOS 6 you can register for the <code>NSUbiquityIdentityDidChangeNotification</code> notification to detect this. Note that this notification does not fire for data resets.</p> <p>Another problem is that setting up your Core Data stack can sometimes be very slow when using iCloud. The call to <code>addPersistentStoreWithType:configuration:URL:options:error:</code> sometimes blocks for 5 - 30 seconds. Even if you load your Core Data stack in a background queue, you can&#8217;t access the stored data until this finishes. This makes for a horrible user experience. (<a href="https://devforums.apple.com/message/745930">Thread on devforums.apple.com</a>)</p> <p>Yet another problem is that on iOS the transaction logs are not downloaded unless your app is in the foreground. So not only is the first launch of your app sometimes slow because of the time it takes to setup the Core Data stack, new data is not immediately available, but slowly trickles in as the transaction logs are downloaded and imported. (<a href="http://openradar.appspot.com/radar?id=2284402">Bugreport on openradar.appspot.com</a>)</p> <p>It would also be really helpful if there was a complete sample project by Apple. <a href="https://developer.apple.com/videos/wwdc/2012/?id=227">Session 227</a> from WWDC 2012 covers the basics, but the presented sample project is incomplete and doesn&#8217;t properly handle iCloud resets.</p> <h1>The Future</h1> <p>iOS 6 brought some improvements to Core Data iCloud storage and Apple developers have acknowledged that there are still problems. Hopefully by the time iOS 7 is released Core Data iCloud storage will be a rock solid API. It isn&#8217;t unusable right now, but be prepared to run into problems when using it in your app.</p> <div class="footnote"> <hr /> <ol> <li id="fn:nosync"> <p>You can store the database inside the ubiquity container, but it must be inside a <code>*.nosync</code> folder. Apple recommends you do this so that the database is automatically removed when the user logs out of iCloud or logs in to a different account.&#160;<a class="footnote-backref" href="#fnref:nosync" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p> </li> </ol> </div>Getting Location Data for an UIImage2012-11-23T00:00:00+01:00Julien Poissonniertag:blog.caffeine.lu,2012-11-23:getting-location-data-for-an-uiimage.html<p>The images you get from the <code>imagePickerController:didFinishPickingMediaWithInfo:</code> UIImagePickerController delegate method do not contain location data. To access this metadata you have to go through the AssetsLibrary framework and the latitude and longitude can then be found under the <code>{GPS}</code> key of the metadata dictionary.</p> <div class="codehilite"><pre><span></span><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">imagePickerController:</span><span class="p">(</span><span class="bp">UIImagePickerController</span> <span class="o">*</span><span class="p">)</span><span class="nv">picker</span> <span class="nf">didFinishPickingMediaWithInfo:</span><span class="p">(</span><span class="bp">NSDictionary</span> <span class="o">*</span><span class="p">)</span><span class="nv">info</span> <span class="p">{</span> <span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="n">info</span><span class="p">[</span><span class="s">@&quot;UIImagePickerControllerReferenceURL&quot;</span><span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="p">{</span> <span class="bp">ALAssetsLibrary</span> <span class="o">*</span><span class="n">lib</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">ALAssetsLibrary</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span> <span class="p">[</span><span class="n">lib</span> <span class="nl">assetForURL</span><span class="p">:</span><span class="n">url</span> <span class="nl">resultBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">ALAsset</span> <span class="o">*</span><span class="n">asset</span><span class="p">)</span> <span class="p">{</span> <span class="bp">NSDictionary</span> <span class="o">*</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">asset</span><span class="p">.</span><span class="n">defaultRepresentation</span><span class="p">.</span><span class="n">metadata</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">metadata</span><span class="p">)</span> <span class="p">{</span> <span class="bp">NSNumber</span> <span class="o">*</span><span class="n">longitude</span> <span class="o">=</span> <span class="n">metadata</span><span class="p">[</span><span class="s">@&quot;{GPS}&quot;</span><span class="p">][</span><span class="s">@&quot;Longitude&quot;</span><span class="p">];</span> <span class="bp">NSNumber</span> <span class="o">*</span><span class="n">latitude</span> <span class="o">=</span> <span class="n">metadata</span><span class="p">[</span><span class="s">@&quot;{GPS}&quot;</span><span class="p">][</span><span class="s">@&quot;Latitude&quot;</span><span class="p">];</span> <span class="k">if</span> <span class="p">(</span><span class="n">longitude</span> <span class="o">&amp;&amp;</span> <span class="n">latitude</span><span class="p">)</span> <span class="p">{</span> <span class="n">CLLocationCoordinate2D</span> <span class="n">coord</span> <span class="o">=</span> <span class="n">CLLocationCoordinate2DMake</span><span class="p">([</span><span class="n">latitude</span> <span class="n">doubleValue</span><span class="p">],</span> <span class="p">[</span><span class="n">longitude</span> <span class="n">doubleValue</span><span class="p">]);</span> <span class="c1">// do something with coordinate</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="nl">failureBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//User denied access</span> <span class="n">NSLog</span><span class="p">(</span><span class="s">@&quot;Unable to access image: %@&quot;</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span> <span class="p">}];</span> <span class="p">}</span> <span class="p">}</span> </pre></div> <p>The first time <code>assetForUrl:resultBlock:</code> is called the user will be asked to authorize the access.</p>Show/Hide Invisible Chars in Vim2012-11-22T00:00:00+01:00Julien Poissonniertag:blog.caffeine.lu,2012-11-22:showhide-invisible-chars-in-vim.html<p><a href="http://vimhelp.appspot.com/options.txt.html#%27listchars%27"><code>listchars</code></a> lets you define replacements for unprintable characters to display in list mode. List mode can then be toggled on and off using <code>:set list!</code></p> <div class="codehilite"><pre><span></span><span class="k">set</span> <span class="nb">listchars</span><span class="p">=</span><span class="k">tab</span>:▸<span class="p">,</span><span class="nb">eol</span>:¬ <span class="nb">nnoremap</span> <span class="p">&lt;</span>leader<span class="p">&gt;</span><span class="k">i</span> :<span class="k">set</span> <span class="nb">list</span><span class="p">!&lt;</span>CR<span class="p">&gt;</span> </pre></div> <p><img alt="listchars in action" src="static/images/listchars.png" /></p>