<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.4.2">Jekyll</generator><link href="https://ksuther.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ksuther.com/" rel="alternate" type="text/html" /><updated>2017-05-14T12:41:17-04:00</updated><id>https://ksuther.com/</id><title type="html">Kent Sutherland</title><subtitle>Mac and iOS development and possibly other miscellaneous things
</subtitle><entry><title type="html">Using Hammerspoon to fix Revert Changes</title><link href="https://ksuther.com/2017/05/13/using-hammerspoon-to-fix-revert-changes" rel="alternate" type="text/html" title="Using Hammerspoon to fix Revert Changes" /><published>2017-05-13T08:00:00-04:00</published><updated>2017-05-13T08:00:00-04:00</updated><id>https://ksuther.com/2017/05/13/using-hammerspoon-to-fix-revert-changes</id><content type="html" xml:base="https://ksuther.com/2017/05/13/using-hammerspoon-to-fix-revert-changes">&lt;p&gt;A few years ago Apple added Auto-Save and Versions (in Lion), which resulted in doing away with the traditional Save, Don’t Save, Cancel prompt that would appear when closing a document with unsaved changes. If you dislike Auto-Save, it’s still possible to disable Auto-Save and get prompted when closing a document. While this disables Auto-Save, the old Don’t Save button now says Revert Changes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The traditional macOS save prompt with Don’t Save&lt;/em&gt;&lt;br /&gt;
&lt;img src=&quot;/assets/2017/05/dontsave.jpg&quot; alt=&quot;The traditional macOS save prompt with Don't Save&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The modern macOS save prompt with Revert Changes&lt;/em&gt;&lt;br /&gt;
&lt;img src=&quot;/assets/2017/05/revertchanges.jpg&quot; alt=&quot;The modern macOS save prompt with Revert Changes&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The name change alone isn’t a problem, but with this change the command-D shortcut to select Don’t Save no longer exists. Even worse, there’s no replacement. This change was almost five years ago, so I’m not holding out much hope for a new shortcut to arrive.&lt;/p&gt;

&lt;p&gt;After far too many times of closing a document with changes and having to reach for the mouse to click Revert Changes, I finally started looking for a solution. I recently started using &lt;a href=&quot;http://www.hammerspoon.org&quot;&gt;Hammerspoon&lt;/a&gt; to perform some basic automation tasks with keyboard shortcuts. Hammerspoon has extensive support for window manipulation through the system’s accessibility API, so I thought it might be capable of simulating a click on the Revert Changes button. I was initially disappointed to find that Hammerspoon’s accessibility API is limited to just windows, but I found an &lt;a href=&quot;https://github.com/asmagill/hs._asm.axuielement/&quot;&gt;experimental module&lt;/a&gt; that gives full access to the system’s accessibility API.&lt;/p&gt;

&lt;p&gt;After installing the &lt;a href=&quot;https://github.com/asmagill/hs._asm.axuielement/&quot;&gt;axuielement module&lt;/a&gt; in &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.hammerspoon&lt;/code&gt; I was put together a hot key that could click on Revert Changes, no mouse needed:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;hs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;alt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ctrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;R&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axui&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hs._asm.axuielement&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;revertButton&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;windowElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focusedWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()):&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;elementSearch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AXButton&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Revert Changes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;revertButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;performAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AXPress&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If you’re not familiar with Hammerspoon or Lua, this is a script that goes in &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.hammerspoon/init.lua&lt;/code&gt;. It registers a hot key that searches the current window for a button named Revert Changes, then clicks on it.&lt;/p&gt;

&lt;p&gt;Now when a save prompt appears I can press control-option-R to dismiss it, no mouse needed. One small issue with this approach is it is a global hot key, which means it is always active so you have to choose a hot key that isn’t used for anything else. It would be even better to use a hot key such as command-R, but it would conflict with regular command-R shortcuts (such as Reload Page in Safari).&lt;/p&gt;

&lt;p&gt;Maybe someday we’ll get a replacement system shortcut (31936893 for anyone who wants to file a bug with Apple), but until then at least I have an alternative.&lt;/p&gt;</content><author><name></name></author><summary type="html">A few years ago Apple added Auto-Save and Versions (in Lion), which resulted in doing away with the traditional Save, Don’t Save, Cancel prompt that would appear when closing a document with unsaved changes. If you dislike Auto-Save, it’s still possible to disable Auto-Save and get prompted when closing a document. While this disables Auto-Save, the old Don’t Save button now says Revert Changes.</summary></entry><entry><title type="html">A slimmer iOS Simulator: SimulatorBorderKiller</title><link href="https://ksuther.com/2013/12/12/a-slimmer-ios-simulator-simulatorborderkiller" rel="alternate" type="text/html" title="A slimmer iOS Simulator: SimulatorBorderKiller" /><published>2013-12-12T14:33:55-05:00</published><updated>2013-12-12T14:33:55-05:00</updated><id>https://ksuther.com/2013/12/12/a-slimmer-ios-simulator-simulatorborderkiller</id><content type="html" xml:base="https://ksuther.com/2013/12/12/a-slimmer-ios-simulator-simulatorborderkiller">&lt;p&gt;Back when the iPhone SDK first became available, the fake iPhone bordering the simulator gave that extra thrill reminding everyone that yes, we were in fact making software for the iPhone. That initial excitement has long worn off, but the device border is still there. My workaround has been to run the simulator at 75% scale, which also hides the device border, but that stopped working after jumping on the Retina Mac bandwagon last month. Since the iOS Simulator adopts the scale of the Mac's screen, running the simulator at anything but 100% results in a tiny window. Unfortunately this also means the useless device border is back.&lt;/p&gt;
&lt;p&gt;Thanks to a couple of hours of digging around with class-dump, otx, and lldb, the clutter of fake iPhones and iPads is gone for good. The iOS Simulator's title bar has also gained an orientation status, as I have a tendency to forget which way is up (and who can ever remember the difference between landscape left and landscape right?).&lt;/p&gt;
&lt;p&gt;Borderless simulator bliss is now just a SIMBL plugin away. You'll also need a SIMBL injection tool, such as &lt;a href=&quot;https://github.com/norio-nomura/EasySIMBL/&quot;&gt;EasySIMBL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ksuther/SimulatorBorderKiller/&quot;&gt;SimulatorBorderKiller on GitHub&lt;/a&gt;&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">Back when the iPhone SDK first became available, the fake iPhone bordering the simulator gave that extra thrill reminding everyone that yes, we were in fact making software for the iPhone. That initial excitement has long worn off, but the device border is still there. My workaround has been to run the simulator at 75% scale, which also hides the device border, but that stopped working after jumping on the Retina Mac bandwagon last month. Since the iOS Simulator adopts the scale of the Mac's screen, running the simulator at anything but 100% results in a tiny window. Unfortunately this also means the useless device border is back. Thanks to a couple of hours of digging around with class-dump, otx, and lldb, the clutter of fake iPhones and iPads is gone for good. The iOS Simulator's title bar has also gained an orientation status, as I have a tendency to forget which way is up (and who can ever remember the difference between landscape left and landscape right?). Borderless simulator bliss is now just a SIMBL plugin away. You'll also need a SIMBL injection tool, such as EasySIMBL. SimulatorBorderKiller on GitHub</summary></entry><entry><title type="html">Automating iOS App Store screenshots</title><link href="https://ksuther.com/2013/02/24/automating-ios-app-store-screenshots" rel="alternate" type="text/html" title="Automating iOS App Store screenshots" /><published>2013-02-24T13:52:22-05:00</published><updated>2013-02-24T13:52:22-05:00</updated><id>https://ksuther.com/2013/02/24/automating-ios-app-store-screenshots</id><content type="html" xml:base="https://ksuther.com/2013/02/24/automating-ios-app-store-screenshots">&lt;p&gt;This originated from a short talk about screenshot automation that I gave at the &lt;a href=&quot;http://www.cocoaheads.org/us/BostonMassachusetts/index.html&quot;&gt;Boston CocoaHeads&lt;/a&gt; in January. My initial goal of the talk was to just show that it was possible to do such a thing and encourage others to consider automating their own processes, but there was some interest in a more detailed write-up. Here it is. Also, thanks to Daniel Jalkut for his &lt;a href=&quot;http://bitsplitting.org/2013/01/11/screenshot-lightning/&quot;&gt;blog post&lt;/a&gt; that stirred up some more interest.&lt;/p&gt;
&lt;h3&gt;What does this look like?&lt;/h3&gt;
&lt;p&gt;First off, what am I talking about? Here's a video of Fantastical's screenshot automation, which shows the complete process in action.&lt;/p&gt;
&lt;p&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/RUgUcvx0Ugg?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;h3&gt;Why do I want to do this?&lt;/h3&gt;
&lt;p&gt;Because you're lazy. Why take screenshots manually when your computer can do it for you? For one, consider the math. Let's say you have 5 screenshots for the App Store, for 5 languages. Oh yeah, you also have a 3.5 inch and a 4 inch screen. Maybe an iPad too. That's 5 x 5 x 2 (or 3) screenshots to take. At 30 seconds a screenshot, that's 25 (or 37.5) minutes just to take the screenshots. Don't make any mistakes, otherwise it'll take even longer. This probably isn't a one time deal either, unless you never plan on changing your app again. Trust me, this is worth taking a couple of hours to add to your app. As you'll see, I've even done some of the work for you.&lt;/p&gt;
&lt;h3&gt;OK, show me an example&lt;/h3&gt;
&lt;p&gt;First, grab the source from &lt;a href=&quot;https://github.com/ksuther/KSScreenshotManager&quot;&gt;KSScreenshotManager at GitHub&lt;/a&gt;. Be sure you clone the WaxSim submodule, otherwise the included script won't work. For those of you who aren't familiar with submodules, the command you're looking for is &lt;code&gt;git submodule update --init&lt;/code&gt;. If you want to include this in your own project, add KSScreenshotManager as a submodule and add &lt;code&gt;KSScreenshotManager&lt;/code&gt; and &lt;code&gt;KSScreenshotAction&lt;/code&gt; to your project.&lt;/p&gt;
&lt;h3&gt;Safety first&lt;/h3&gt;
&lt;p&gt;Be aware that we'll be using private API to get the job done. This doesn't matter since this code isn't going to the App Store, but take care that you don't let private API declarations or usage slip into your shipping builds. You'll notice that the example uses the macro &lt;code&gt;CREATING_SCREENSHOTS&lt;/code&gt; to ensure that none of the screenshot code is included in normal builds.&lt;/p&gt;
&lt;h3&gt;Defining your screenshots&lt;/h3&gt;
&lt;p&gt;Digging into the sample code, &lt;code&gt;KSScreenshotManager&lt;/code&gt; and the &lt;code&gt;MyScreenshotManager&lt;/code&gt; subclass are what we're interested in. This is where we specify what we actually want to take screenshots of in our app. In our example we're going to take two screenshots of a table view.&lt;/p&gt;
&lt;p&gt;Our first action scrolls the table view to the second row. Once &lt;code&gt;actionBlock&lt;/code&gt; is called, &lt;code&gt;KSScreenshotManager&lt;/code&gt; will take a screenshot and crop out the status bar.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-objc&quot; data-lang=&quot;objc&quot;&gt;&lt;span class=&quot;n&quot;&gt;KSScreenshotAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;synchronousAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KSScreenshotAction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;actionWithName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;tableView1&quot;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;asynchronous&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actionBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSIndexPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSIndexPath&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;indexPathForRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;inSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tableViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tableView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;scrollToRowAtIndexPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexPath&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;atScrollPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UITableViewScrollPositionTop&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cleanupBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tableViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tableView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setContentOffset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CGPointZero&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}];&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addScreenshotAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;synchronousAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The next action is similar, but this time &lt;code&gt;asynchronous&lt;/code&gt; is &lt;code&gt;YES&lt;/code&gt;. This allows us to perform actions that take time to complete. Once the screenshot is ready, call &lt;code&gt;[self actionIsReady]&lt;/code&gt;. This will take the screenshot and continue to the next action. Here we're just changing the device orientation, but you might need to wait for other reasons, such as animations or network activity.&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-objc&quot; data-lang=&quot;objc&quot;&gt;&lt;span class=&quot;n&quot;&gt;KSScreenshotAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asynchronousAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KSScreenshotAction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;actionWithName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;tableView2&quot;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;asynchronous&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;YES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actionBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSIndexPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSIndexPath&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;indexPathForRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;inSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tableViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;tableView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;scrollToRowAtIndexPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexPath&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;atScrollPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UITableViewScrollPositionTop&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIDevice&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;currentDevice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setOrientation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UIInterfaceOrientationLandscapeLeft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//programmatically switch to landscape (private API)
&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dispatch_time_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;popTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DISPATCH_TIME_NOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int64_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSEC_PER_SEC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dispatch_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;popTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_get_main_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;actionIsReady&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cleanupBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Once the actions are created, we need to actually create the screenshots. We do that in &lt;code&gt;-[AppDelegate application:didFinishLaunchingWithOptions:]&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-objc&quot; data-lang=&quot;objc&quot;&gt;&lt;span class=&quot;n&quot;&gt;dispatch_time_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;popTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DISPATCH_TIME_NOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int64_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSEC_PER_SEC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dispatch_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;popTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dispatch_get_main_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MyScreenshotManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshotManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MyScreenshotManager&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshotManager&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setTableViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshotManager&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;takeScreenshots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3&gt;Driving the simulator&lt;/h3&gt;
&lt;p&gt;So we have the screenshot actions set up, but we still have to manually change the project target and run the app in the simulator. Fortunately we can automate this too, thanks to WaxSim. Using &lt;code&gt;make_screenshots.py&lt;/code&gt; we can generate screenshots for any combination of devices and languages. The version of &lt;code&gt;make_screenshots.py&lt;/code&gt; included with the sample code runs for the 3.5 inch and 4 inch iPhone in English and German, for a total of 4 runs. You'll need to change the variables in &lt;code&gt;make_screenshots.py&lt;/code&gt; to make it work with your own project.&lt;/p&gt;
&lt;p&gt;After running &lt;code&gt;python make_screenshots.py ~/Desktop/screenshots&lt;/code&gt; in the Terminal, we have a fresh set of screenshots:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/2013/02/screenshot-output.jpg&quot; alt=&quot;Screenshot output&quot; width=&quot;575&quot; height=&quot;355&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That's all there is to it! Any time you need screenshots, just run that script again and wait about a minute. For bonus points you can hook this up to your continuous integration server so you always have up-to-date screenshots.&lt;/p&gt;
&lt;h3&gt;Getting fancier&lt;/h3&gt;
&lt;p&gt;What you've just seen is enough to automate screenshots in your own app. However, it can be tricky to get your app just into the right state to make a screenshot. For example, &lt;a href=&quot;http://flexibits.com/fantastical-iphone&quot;&gt;Fantastical's&lt;/a&gt; screenshots had to have the exact same set of events and be running on a certain date. This took a bit more than just displaying view controllers and adjusting views. Here's some additional details on what I did to get Fantastical's screenshot process running smoothly. These won't apply to every app directly, but hopefully it'll provide some ideas.&lt;/p&gt;
&lt;h4&gt;Faking the date and time&lt;/h4&gt;
&lt;p&gt;The pesky thing about time is it won't stay still. Not so helpful for screenshots of time-sensitive material such as calendars. Fortunately it's easy enough to fake the date throughout an application without actually changing any code. &lt;a href=&quot;http://cocoadev.com/wiki/MethodSwizzling&quot;&gt;Method swizzling&lt;/a&gt; to the rescue!&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-objc&quot; data-lang=&quot;objc&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#import &amp;lt;objc/runtime.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NSDate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;ScreenshotSwizzle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SEL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;originalSelector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SEL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSelector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshot_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;origMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class_getClassMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;originalSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Method&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class_getClassMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;method_exchangeImplementations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;origMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenshot_date&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//Today is November 14, 2012
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dateWithTimeIntervalSince1970&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1352894400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Now the entire app thinks it is November 14 all the time. If you find yourself thinking &quot;I wish I could change what this method does everywhere in the app,&quot; think swizzling.&lt;/p&gt;
&lt;h4&gt;Abusing private API&lt;/h4&gt;
&lt;p&gt;There are all kinds of extra goodies available since this code isn't going to the App Store. In the example above, I used the private &lt;code&gt;-[UIDevice setOrientation:]&lt;/code&gt; to force the simulator into a difference orientation. In Fantastical, private API ended up being useful for setting up consistent calendar data. Rather than creating the events by hand using EventKit's public API, &lt;a href=&quot;http://stevenygard.com/projects/class-dump/&quot;&gt;class-dump&lt;/a&gt; revealed that &lt;code&gt;EKEventStore&lt;/code&gt; had methods to load ics files already lurking in it. One private method later, I had events getting loaded from an ics file:&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-objc&quot; data-lang=&quot;objc&quot;&gt;&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EKEventStore&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;importICSData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intoCalendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;NSData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;icsData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSData&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dataWithContentsOfURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSBundle&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mainBundle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;URLForResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;Home&quot;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;withExtension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;ics&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]];&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;NSArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eventStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;importICSData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;icsData&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;intoCalendar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;calendar&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This made it possible to create the events using a more sensible application and then feed the ics data straight into EventKit. Little things like this give even more time savings that you might not think of if you're taking screenshots by hand. Again, take care to ensure you don't let any private API leak into your App Store code.&lt;/p&gt;
&lt;h4&gt;Loading network data&lt;/h4&gt;
&lt;p&gt;Rapidly-changing network data can be problematic when you're trying to make that perfect set of screenshots. While this wasn't necessarily in Fantastical, using a mock object such as &lt;a href=&quot;https://github.com/square/objc-mocktail&quot;&gt;Mocktail&lt;/a&gt; could make life a lot easier. (Disclaimer: I've never used Mocktail myself, but it looks handy.)&lt;/p&gt;
&lt;h3&gt;Other options&lt;/h3&gt;
&lt;p&gt;Prefer using the UI Automation instrument? &lt;a href=&quot;https://github.com/jonathanpenn/ui-screen-shooter&quot;&gt;UI Screen Shooter&lt;/a&gt; may be of interest to you. I'm partial to my approach since I needed the additional control of setting NSUserDefaults based on locale, swizzling NSDate and loading specific calendar data on each launch. However, UI Automation may be more appropriate in some situations.&lt;/p&gt;
&lt;h3&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Get the code for &lt;a href=&quot;https://github.com/ksuther/KSScreenshotManager&quot;&gt;KSScreenshotManager at GitHub&lt;/a&gt;. As you've seen, you won't be able to drop this code in and magically have automated screenshots in your own app. You still need to get your app into the right state to take the screenshots. The good news is that you only have to do it once. Happy automating!&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">This originated from a short talk about screenshot automation that I gave at the Boston CocoaHeads in January. My initial goal of the talk was to just show that it was possible to do such a thing and encourage others to consider automating their own processes, but there was some interest in a more detailed write-up. Here it is. Also, thanks to Daniel Jalkut for his blog post that stirred up some more interest. What does this look like? First off, what am I talking about? Here's a video of Fantastical's screenshot automation, which shows the complete process in action. Why do I want to do this? Because you're lazy. Why take screenshots manually when your computer can do it for you? For one, consider the math. Let's say you have 5 screenshots for the App Store, for 5 languages. Oh yeah, you also have a 3.5 inch and a 4 inch screen. Maybe an iPad too. That's 5 x 5 x 2 (or 3) screenshots to take. At 30 seconds a screenshot, that's 25 (or 37.5) minutes just to take the screenshots. Don't make any mistakes, otherwise it'll take even longer. This probably isn't a one time deal either, unless you never plan on changing your app again. Trust me, this is worth taking a couple of hours to add to your app. As you'll see, I've even done some of the work for you. OK, show me an example First, grab the source from KSScreenshotManager at GitHub. Be sure you clone the WaxSim submodule, otherwise the included script won't work. For those of you who aren't familiar with submodules, the command you're looking for is git submodule update --init. If you want to include this in your own project, add KSScreenshotManager as a submodule and add KSScreenshotManager and KSScreenshotAction to your project. Safety first Be aware that we'll be using private API to get the job done. This doesn't matter since this code isn't going to the App Store, but take care that you don't let private API declarations or usage slip into your shipping builds. You'll notice that the example uses the macro CREATING_SCREENSHOTS to ensure that none of the screenshot code is included in normal builds. Defining your screenshots Digging into the sample code, KSScreenshotManager and the MyScreenshotManager subclass are what we're interested in. This is where we specify what we actually want to take screenshots of in our app. In our example we're going to take two screenshots of a table view. Our first action scrolls the table view to the second row. Once actionBlock is called, KSScreenshotManager will take a screenshot and crop out the status bar.</summary></entry><entry><title type="html">KSImageNamed: Xcode autocomplete for imageNamed</title><link href="https://ksuther.com/2013/01/22/ksimagenamed-xcode-autocomplete-for-imagenamed" rel="alternate" type="text/html" title="KSImageNamed: Xcode autocomplete for imageNamed" /><published>2013-01-22T01:18:59-05:00</published><updated>2013-01-22T01:18:59-05:00</updated><id>https://ksuther.com/2013/01/22/ksimagenamed-xcode-autocomplete-for-imagenamed</id><content type="html" xml:base="https://ksuther.com/2013/01/22/ksimagenamed-xcode-autocomplete-for-imagenamed">&lt;p&gt;A few months ago I discovered and installed &lt;a href=&quot;https://github.com/omz/ColorSense-for-Xcode&quot;&gt;ColorSense for Xcode&lt;/a&gt;, which set my mind to thinking what other common annoyances or inconveniences be improved in Xcode. It took me about three and a half seconds to jump from colors to images, as Xcode doesn't autocomplete &lt;code&gt;imageNamed:&lt;/code&gt; calls. It's impossible to remember the names of all the images in a project, especially when there are a hundred or more images. Switching to the Project Navigator to check if the image is named &lt;code&gt;button-highlighted-pressed.png&lt;/code&gt; or &lt;code&gt;button-pressed-highlighted.png&lt;/code&gt; gets old quickly. Additional trips to check whether an image's 2x representation is added to the project only makes matters worse.&lt;/p&gt;
&lt;p&gt;Armed with a new idea, a few free hours, and &lt;a href=&quot;http://stevenygard.com/projects/class-dump/&quot;&gt;class-dump&lt;/a&gt;, I sat down and started hacking. I figured out how to insert items into Xcode's autocomplete lists, ran into a couple of roadblocks, quit for the day, and then promptly forgot about the matter entirely. And so the project sat and collected dust, as code is prone to do. Finally, last week I forgot the name of one image too many. So I now present to you KSImageNamed, an Xcode plug-in for getting the &lt;code&gt;imageNamed:&lt;/code&gt; autocomplete that should have been there in the first place.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/2013/01/KSImageNamed.png&quot; alt=&quot;KSImageNamed in action&quot; width=&quot;458&quot; height=&quot;215&quot; class=&quot;alignnone size-full wp-image-247&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Want &lt;code&gt;NSImage/UIImage imageNamed:&lt;/code&gt; autocomplete bliss in your copy of Xcode? Go clone and build &lt;a href=&quot;https://github.com/ksuther/KSImageNamed-Xcode&quot;&gt;KSImageNamed on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hopefully this will save you a little frustration the next time you type &lt;code&gt;imageNamed:&lt;/code&gt; and can't remember the name of the image you were about to use. Even better, maybe this will get some people thinking about scratching an itch and making Xcode a little more pleasant to use. If there's any interest, I may do a follow-up with some details on how the plug-in works so others can start hacking on Xcode themselves.&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">A few months ago I discovered and installed ColorSense for Xcode, which set my mind to thinking what other common annoyances or inconveniences be improved in Xcode. It took me about three and a half seconds to jump from colors to images, as Xcode doesn't autocomplete imageNamed: calls. It's impossible to remember the names of all the images in a project, especially when there are a hundred or more images. Switching to the Project Navigator to check if the image is named button-highlighted-pressed.png or button-pressed-highlighted.png gets old quickly. Additional trips to check whether an image's 2x representation is added to the project only makes matters worse. Armed with a new idea, a few free hours, and class-dump, I sat down and started hacking. I figured out how to insert items into Xcode's autocomplete lists, ran into a couple of roadblocks, quit for the day, and then promptly forgot about the matter entirely. And so the project sat and collected dust, as code is prone to do. Finally, last week I forgot the name of one image too many. So I now present to you KSImageNamed, an Xcode plug-in for getting the imageNamed: autocomplete that should have been there in the first place. Want NSImage/UIImage imageNamed: autocomplete bliss in your copy of Xcode? Go clone and build KSImageNamed on GitHub. Hopefully this will save you a little frustration the next time you type imageNamed: and can't remember the name of the image you were about to use. Even better, maybe this will get some people thinking about scratching an itch and making Xcode a little more pleasant to use. If there's any interest, I may do a follow-up with some details on how the plug-in works so others can start hacking on Xcode themselves.</summary></entry><entry><title type="html">Cleaning up and starting over</title><link href="https://ksuther.com/2013/01/21/cleaning-up-and-starting-over" rel="alternate" type="text/html" title="Cleaning up and starting over" /><published>2013-01-21T23:35:18-05:00</published><updated>2013-01-21T23:35:18-05:00</updated><id>https://ksuther.com/2013/01/21/cleaning-up-and-starting-over</id><content type="html" xml:base="https://ksuther.com/2013/01/21/cleaning-up-and-starting-over">&lt;p&gt;It's been almost two years since I've made any changes at all to this website, so it's finally time to do something about the cobwebs that have gathered. There have been occasions where I've wanted a place to post something small and simple, but the sole orientation of this site towards Chax and Warp has sort of prevented me from doing so.&lt;/p&gt;
&lt;p&gt;Almost 100% of my development work in the last two and a half years has been consumed by &lt;a href=&quot;http://flexibits.com&quot;&gt;Flexibits&lt;/a&gt;, where &lt;a href=&quot;http://twitter.com/macguitar&quot;&gt;Michael Simmons&lt;/a&gt; and I have been busy building &lt;a href=&quot;http://flexibits.com/fantastical&quot;&gt;Fantastical&lt;/a&gt; and other apps. While this has resulted in some great new apps for everyone to use, it also means Chax and Warp have been completely ignored. Neither projects have been in active development for almost three years now, so it's time to clean things up here and move those projects officially into storage. Functionally this means nothing is going to change, so this is more of an official acknowledgement of reality so I don't have to feel guilty about ignoring them anymore.&lt;/p&gt;
&lt;p&gt;Chax and Warp are still available for older versions of Mac OS X (10.4-10.6 for Chax, 10.5 and 10.6 for Warp), so they haven't disappeared completely. The source code for &lt;a href=&quot;https://github.com/ksuther/Chax&quot;&gt;Chax&lt;/a&gt; and &lt;a href=&quot;https://github.com/ksuther/Warp&quot;&gt;Warp&lt;/a&gt; are also available on GitHub in case anyone ever wants to try to breathe new life into them. At the very least, the source code to Chax is an interesting case study in using the Objective-C runtime to make large-scale modifications to another application.&lt;/p&gt;
&lt;p&gt;I'd like to think that someday I'll bring back Chax in a very limited fashion, if only to scratch my own itches with the rather uninspiring experience that is Messages. That is what Chax originally started as, but the sheer number of features and additions made it harder and harder to maintain in the face of ever-faster system updates. If Chax were to ever come back, it would be a back-to-basics version, changing just the things that annoy me the most.&lt;/p&gt;
&lt;p&gt;Keep an eye out here for future posts, probably mostly related to Mac or iOS development in some form.&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">It's been almost two years since I've made any changes at all to this website, so it's finally time to do something about the cobwebs that have gathered. There have been occasions where I've wanted a place to post something small and simple, but the sole orientation of this site towards Chax and Warp has sort of prevented me from doing so. Almost 100% of my development work in the last two and a half years has been consumed by Flexibits, where Michael Simmons and I have been busy building Fantastical and other apps. While this has resulted in some great new apps for everyone to use, it also means Chax and Warp have been completely ignored. Neither projects have been in active development for almost three years now, so it's time to clean things up here and move those projects officially into storage. Functionally this means nothing is going to change, so this is more of an official acknowledgement of reality so I don't have to feel guilty about ignoring them anymore. Chax and Warp are still available for older versions of Mac OS X (10.4-10.6 for Chax, 10.5 and 10.6 for Warp), so they haven't disappeared completely. The source code for Chax and Warp are also available on GitHub in case anyone ever wants to try to breathe new life into them. At the very least, the source code to Chax is an interesting case study in using the Objective-C runtime to make large-scale modifications to another application. I'd like to think that someday I'll bring back Chax in a very limited fashion, if only to scratch my own itches with the rather uninspiring experience that is Messages. That is what Chax originally started as, but the sheer number of features and additions made it harder and harder to maintain in the face of ever-faster system updates. If Chax were to ever come back, it would be a back-to-basics version, changing just the things that annoy me the most. Keep an eye out here for future posts, probably mostly related to Mac or iOS development in some form.</summary></entry><entry><title type="html">Warp 1.2.3</title><link href="https://ksuther.com/2011/03/07/warp-1-2-3" rel="alternate" type="text/html" title="Warp 1.2.3" /><published>2011-03-07T23:46:12-05:00</published><updated>2011-03-07T23:46:12-05:00</updated><id>https://ksuther.com/2011/03/07/warp-1-2-3</id><content type="html" xml:base="https://ksuther.com/2011/03/07/warp-1-2-3">&lt;ul&gt;
&lt;li&gt;Fixed a crash that could occur when warping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksuther.com/warp/downloads/Warp_1.2.3.dmg&quot;&gt;Download Warp 1.2.2&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://github.com/ksuther/Warp&quot;&gt;Warp on GitHub&lt;/a&gt;&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">Fixed a crash that could occur when warping. Download Warp 1.2.2 Warp on GitHub</summary></entry><entry><title type="html">Warp 1.2.2</title><link href="https://ksuther.com/2011/01/16/warp-1-2-2" rel="alternate" type="text/html" title="Warp 1.2.2" /><published>2011-01-16T15:46:11-05:00</published><updated>2011-01-16T15:46:11-05:00</updated><id>https://ksuther.com/2011/01/16/warp-1-2-2</id><content type="html" xml:base="https://ksuther.com/2011/01/16/warp-1-2-2">&lt;ul&gt;
&lt;li&gt;Fixed caps lock preventing Warp from activating.&lt;/li&gt;
&lt;li&gt;Warp no longer activates when in a fullscreen application such as a game.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksuther.com/warp/downloads/Warp_1.2.2.dmg&quot;&gt;Download Warp 1.2.2&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://github.com/ksuther/Warp&quot;&gt;Warp on GitHub&lt;/a&gt;&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">Fixed caps lock preventing Warp from activating. Warp no longer activates when in a fullscreen application such as a game. Download Warp 1.2.2 Warp on GitHub</summary></entry><entry><title type="html">Chax 3.0.2</title><link href="https://ksuther.com/2010/03/08/chax-3-0-2" rel="alternate" type="text/html" title="Chax 3.0.2" /><published>2010-03-08T00:39:22-05:00</published><updated>2010-03-08T00:39:22-05:00</updated><id>https://ksuther.com/2010/03/08/chax-3-0-2</id><content type="html" xml:base="https://ksuther.com/2010/03/08/chax-3-0-2">&lt;p&gt;Apologies for 3.0.1, I let a pretty major bug slip through. 3.0.2 should fix the problem. Thanks to everyone who let me know about it and were extra helpful with testing.&lt;/p&gt;
&lt;p&gt;Changes from Chax 3.0.1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(Bug) Fixed a serious crash when that could occur if using groups.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksuther.com/chax/downloads/Chax_3.0.2.dmg&quot;&gt;Download Chax 3.0.2&lt;/a&gt;&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">Apologies for 3.0.1, I let a pretty major bug slip through. 3.0.2 should fix the problem. Thanks to everyone who let me know about it and were extra helpful with testing. Changes from Chax 3.0.1: (Bug) Fixed a serious crash when that could occur if using groups. Download Chax 3.0.2</summary></entry><entry><title type="html">Chax 3.0.1</title><link href="https://ksuther.com/2010/03/04/chax-3-0-1" rel="alternate" type="text/html" title="Chax 3.0.1" /><published>2010-03-04T19:24:30-05:00</published><updated>2010-03-04T19:24:30-05:00</updated><id>https://ksuther.com/2010/03/04/chax-3-0-1</id><content type="html" xml:base="https://ksuther.com/2010/03/04/chax-3-0-1">&lt;p&gt;&lt;b&gt;Update&lt;/b&gt;: There appears to be a crashing bug for some people using multiple accounts with groups enabled. If you're experiencing trouble, please try out &lt;a href=&quot;http://ksuther.com/chax/downloads/beta/Chax_3.0.2b3.dmg&quot;&gt;this newer build&lt;/a&gt;. A new update will follow as soon as this problem is resolved.&lt;/p&gt;
&lt;p&gt;This update fixes the most commonly reported bugs I've received in the past few weeks. There are a couple other issues that I'm still working on tracking down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installation for users with networked home folders.&lt;/li&gt;
&lt;li&gt;Chax failing to load after restarting or logging in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please leave a comment if you're continuing to have trouble with either of these issues so I can try to get those problems resolved. Thanks!&lt;/p&gt;
&lt;p&gt;Changes from Chax 3.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(Bug) Fixed cases where iChat would not reconnect properly when waking from sleep&lt;/li&gt;
&lt;li&gt;(Bug) Fixed the unified contact list sometimes appearing blank when reconnecting&lt;/li&gt;
&lt;li&gt;(Bug) Fixed iChat sometimes hanging when contact list auto-resizing is enabled&lt;/li&gt;
&lt;li&gt;(Bug) Fixed tooltips not appearing in the unified contact list&lt;/li&gt;
&lt;li&gt;(Bug) Improved renaming and deleting groups in the unified contact list&lt;/li&gt;
&lt;li&gt;(Bug) Corrupted logs handled better in the log viewer&lt;/li&gt;
&lt;li&gt;(Bug) The selected user is preserved when searching in the log viewer&lt;/li&gt;
&lt;li&gt;(Bug) Minor localization fixes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksuther.com/chax/downloads/Chax_3.0.1.dmg&quot;&gt;Download Chax 3.0.1&lt;/a&gt;&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">Update: There appears to be a crashing bug for some people using multiple accounts with groups enabled. If you're experiencing trouble, please try out this newer build. A new update will follow as soon as this problem is resolved. This update fixes the most commonly reported bugs I've received in the past few weeks. There are a couple other issues that I'm still working on tracking down: Installation for users with networked home folders. Chax failing to load after restarting or logging in. Please leave a comment if you're continuing to have trouble with either of these issues so I can try to get those problems resolved. Thanks! Changes from Chax 3.0: (Bug) Fixed cases where iChat would not reconnect properly when waking from sleep (Bug) Fixed the unified contact list sometimes appearing blank when reconnecting (Bug) Fixed iChat sometimes hanging when contact list auto-resizing is enabled (Bug) Fixed tooltips not appearing in the unified contact list (Bug) Improved renaming and deleting groups in the unified contact list (Bug) Corrupted logs handled better in the log viewer (Bug) The selected user is preserved when searching in the log viewer (Bug) Minor localization fixes Download Chax 3.0.1</summary></entry><entry><title type="html">Chax 3.0</title><link href="https://ksuther.com/2010/02/06/chax-3-0" rel="alternate" type="text/html" title="Chax 3.0" /><published>2010-02-06T17:30:26-05:00</published><updated>2010-02-06T17:30:26-05:00</updated><id>https://ksuther.com/2010/02/06/chax-3-0</id><content type="html" xml:base="https://ksuther.com/2010/02/06/chax-3-0">&lt;p&gt;Here it is, finally!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's new in Chax 3?&lt;/strong&gt;&lt;br /&gt;
Due to the nature of a near-complete rewrite, most of the effort for Chax 3.0 was just in making sure the most important existing features in Chax made a smooth transition over to Snow Leopard. Apple makes significant changes under the hood with every major system update, which means each feature in Chax has to be manually reviewed and tested to make sure everything works. This took significantly longer than I had expected, in part because of the limited amount of time I've spent working on Chax.&lt;/p&gt;
&lt;p&gt;The primary new addition to Chax 3 is a completely rewritten log viewer that takes advantage of Grand Central Dispatch and blocks in order to run faster and more smoothly. In addition to a revamped UI (thanks to &lt;a href=&quot;http://www.twitter.com/macguitar&quot;&gt;Michael Simmons&lt;/a&gt; for his help with the UI), the new log viewer allows you to quickly browse images and links in past conversations without having to manually look through individual logs. I've found the new browsing modes to be very helpful when looking for something in a previous conversation, so I'd encourage everyone who installs Chax 3 to at least check out the new log viewer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's not in Chax 3 that was in Chax 2?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatic message history (iChat on 10.6 now has this feature built-in)&lt;/li&gt;
&lt;li&gt;Automatically go away when the screensaver starts&lt;/li&gt;
&lt;li&gt;Undimming the screen on incoming messages and AV chats&lt;/li&gt;
&lt;li&gt;Moving contacts from one group to another in the unified list does not work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How does Chax 3 load into iChat, and how does this differ from Chax 2?&lt;/strong&gt;&lt;br /&gt;
The short answer is that Chax 2 used an Input Manager and Chax uses a Scripting Addition.&lt;/p&gt;
&lt;p&gt;Before 10.6 came out, Input Managers were generally the most popular method of hacking and extending the functionality of applications such as iChat and Safari. However, Input Managers have been slowly phased out by Apple; first heavily restricted in 10.5, and completely inoperable under 64-bit in 10.6.&lt;/p&gt;
&lt;p&gt;The removal of Input Managers meant another way of getting into iChat was necessary. Initially the method I used was a separate loader application. This cleanly separated Chax from iChat, but it also proved to have some deficiencies that could not be worked around. The loading mechanism that Chax 3 uses now is a Scripting Addition. The technical details on exactly how Scripting Additions can be used to load into another application was described by &lt;a href=&quot;http://kevin.sb.org/2009/09/02/1password-extension-loading-in-snow-leopard/&quot;&gt;Kevin Ballard a few months ago&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's coming next?&lt;/strong&gt;&lt;br /&gt;
My main priority is ironing out any remaining bugs that are still lurking in this release. Past that, I'd also like to reimplement the remaining features that were in Chax 2 that didn't make it into this release. Past that, the future is hazier. The feature set of Chax has been mostly stagnant over the past few years, as Apple has slowly added more functionality to iChat. This is great for all of us who use iChat every day, but the utility that Chax adds is becoming more and more marginal. It could be in the next major release Chax will be obviated completely, but that time is not here yet.&lt;/p&gt;
&lt;p&gt;There is also the fact that Chax is now over four years old, and my ability to continue to devote as much time as I have in the past to support and development will likely not be able to continue indefinitely. At the moment, I plan to continue to make minor improvements to Chax time allowing, as well as ensure that it is as bug free as possible.&lt;/p&gt;
&lt;p&gt;Changes from Chax 3.0b2:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(New) Dutch localization (thanks to Martijn Körvers)&lt;/li&gt;
&lt;li&gt;(New) Polish localization (thanks to Pawel Labunko)&lt;/li&gt;
&lt;li&gt;(Bug) Can now delete contacts from the unified list when groups are enabled&lt;/li&gt;
&lt;li&gt;(Bug) Fixed Growl notifications not appearing for some people in rare circumstances&lt;/li&gt;
&lt;li&gt;(Misc) Localization updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ksuther.com/chax/downloads/Chax_3.0.dmg&quot;&gt;Download Chax 3.0&lt;/a&gt;&lt;/p&gt;</content><author><name>{&quot;display_name&quot;=&gt;&quot;Kent Sutherland&quot;, &quot;login&quot;=&gt;&quot;kent&quot;, &quot;email&quot;=&gt;&quot;kent.sutherland@gmail.com&quot;, &quot;url&quot;=&gt;&quot;http://www.ksuther.com/&quot;}</name><email>kent.sutherland@gmail.com</email></author><summary type="html">Here it is, finally! What's new in Chax 3? Due to the nature of a near-complete rewrite, most of the effort for Chax 3.0 was just in making sure the most important existing features in Chax made a smooth transition over to Snow Leopard. Apple makes significant changes under the hood with every major system update, which means each feature in Chax has to be manually reviewed and tested to make sure everything works. This took significantly longer than I had expected, in part because of the limited amount of time I've spent working on Chax. The primary new addition to Chax 3 is a completely rewritten log viewer that takes advantage of Grand Central Dispatch and blocks in order to run faster and more smoothly. In addition to a revamped UI (thanks to Michael Simmons for his help with the UI), the new log viewer allows you to quickly browse images and links in past conversations without having to manually look through individual logs. I've found the new browsing modes to be very helpful when looking for something in a previous conversation, so I'd encourage everyone who installs Chax 3 to at least check out the new log viewer. What's not in Chax 3 that was in Chax 2? Automatic message history (iChat on 10.6 now has this feature built-in) Automatically go away when the screensaver starts Undimming the screen on incoming messages and AV chats Moving contacts from one group to another in the unified list does not work How does Chax 3 load into iChat, and how does this differ from Chax 2? The short answer is that Chax 2 used an Input Manager and Chax uses a Scripting Addition. Before 10.6 came out, Input Managers were generally the most popular method of hacking and extending the functionality of applications such as iChat and Safari. However, Input Managers have been slowly phased out by Apple; first heavily restricted in 10.5, and completely inoperable under 64-bit in 10.6. The removal of Input Managers meant another way of getting into iChat was necessary. Initially the method I used was a separate loader application. This cleanly separated Chax from iChat, but it also proved to have some deficiencies that could not be worked around. The loading mechanism that Chax 3 uses now is a Scripting Addition. The technical details on exactly how Scripting Additions can be used to load into another application was described by Kevin Ballard a few months ago. What's coming next? My main priority is ironing out any remaining bugs that are still lurking in this release. Past that, I'd also like to reimplement the remaining features that were in Chax 2 that didn't make it into this release. Past that, the future is hazier. The feature set of Chax has been mostly stagnant over the past few years, as Apple has slowly added more functionality to iChat. This is great for all of us who use iChat every day, but the utility that Chax adds is becoming more and more marginal. It could be in the next major release Chax will be obviated completely, but that time is not here yet. There is also the fact that Chax is now over four years old, and my ability to continue to devote as much time as I have in the past to support and development will likely not be able to continue indefinitely. At the moment, I plan to continue to make minor improvements to Chax time allowing, as well as ensure that it is as bug free as possible. Changes from Chax 3.0b2: (New) Dutch localization (thanks to Martijn Körvers) (New) Polish localization (thanks to Pawel Labunko) (Bug) Can now delete contacts from the unified list when groups are enabled (Bug) Fixed Growl notifications not appearing for some people in rare circumstances (Misc) Localization updates Download Chax 3.0</summary></entry></feed>