tag:blogger.com,1999:blog-25761034309827606182024-03-12T18:28:31.563-07:00@synthesize zach;The personal blog of Zacharias Pasternack. Random Objective-C and Cocoa stuff.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.comBlogger28125tag:blogger.com,1999:blog-2576103430982760618.post-39550346774092281842012-10-10T20:16:00.001-07:002013-04-24T23:11:38.820-07:00Taking advantage of the iPhone 5's larger screen<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/taking-advantage-of-the-iphone-5s-larger-screen/">here</a>. Please update your links.
<hr><br/>
<p><br />Because it comes up on <a href="http://stackoverflow.com">StackOverflow</a>, I dunno, maybe five times per day, I thought I'd make a quick post describing how to make your apps work well with the new iPhone 5 screen dimensions (while still remaining compatible with older iPhones). <br /><br /><p><br />It's really not that hard at all. <a href="http://fat-apps.com/tasklog-ios">TaskLog</a> had iPhone 5 support when it launched. It took me about 5 minutes to make this work, and probably a few additional hours to make it work well. <br /><br /><p><br />The first step is to include an iPhone 5 specific default launch image. This is what iOS uses to determine whether or not your app is ready for the larger screen size.<br /><br /><p><img style="display:block; margin-left:auto; margin-right:auto;" src="http://lh3.ggpht.com/-SuFjmffqoq4/UHY5_nMqIgI/AAAAAAAAADg/IKpBmoLmDis/iphone5-launch-image.png?imgmax=800" alt="iPhone 5 launch image in Xcode" title="iphone5-launch-image.png" border="0" width="600" height="554" /><p><br />It should be 640 x 1136 pixels. If you drag it into the Launch Images in Xcode, it will be automatically named for you. If you just add it to the project yourself, make sure you name it correctly: "Default-568h@2x.png".<br /><br /><p><br />Once you do this, your app will be sized to fit the iPhone 5 screen. From there, the amount of work you need to do depends entirely on how your application is built. If you've set up all your views with proper resizing characteristics (either via <a href="https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/Introduction.html">Auto Layout</a> or the old-school <a href="https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1">autoresizing masks</a>), you might be done. Just make sure to test it out in the Simulator (choose Device->Hardware->iPhone 5 (Retina) ) and make sure all your views look nice. I can't stress this enough: test every one of your views on both 3.5" and 4" devices (or the simulator, at the very least).<br /><br /><p><br />Places where you might have gotchas: if you're doing any kind of hardcoding of coordinates, there might be some tweaking you need to do. If you're going to move or size things in code, based on coordinates, at least try to do so via relative, rather than absolute, coordinates. <br /><br />Related StackOverflow questions:<br /><ul><li><a href="http://stackoverflow.com/questions/12395200/how-to-develop-or-migrate-apps-for-iphone-5-screen-resolution">How to develop or migrate apps for iPhone 5 screen resolution?</a><li><a href="http://stackoverflow.com/questions/12402608/apps-compatible-with-iphone5-and-reverse">Apps compatible with iPhone5 and reverse</a><li><a href="http://stackoverflow.com/questions/12543264/screen-height-compatible-in-iphone5-and-iphone4">Screen height compatible in iphone5 and iphone4</a><li><a href="http://stackoverflow.com/questions/12510881/ios-6-different-storyboards-for-iphone5-and-other-iphones">iOs 6 different storyboards for iphone5 and other iphones</a><li><a href="http://stackoverflow.com/questions/12527517/what-is-the-best-way-for-supporting-both-screen-resolution-of-iphone4-and-iphone">What is the best way for supporting both screen resolution of iPhone4 and iPhone5 ? - Auto layout in only iOS6</a><li><a href="http://stackoverflow.com/questions/12584376/xcode-storyboard-displaying-the-new-iphone-5-screen-size">Xcode Storyboard displaying the new iPhone 5 screen size?</a><li><a href="http://stackoverflow.com/questions/12396545/how-to-deal-with-iphone-5-screen-size">How to deal with iPhone 5 screen size?</a><li><a href="http://stackoverflow.com/questions/12553521/obj-c-how-do-i-get-the-height-of-a-sub-view-to-scale-across-all-iphone-sizes">Obj-C, how do I get the height of a sub view to scale, across all iPhone sizes?</a><li><a href="http://stackoverflow.com/questions/12504541/ios-6-4-inch-device-screen-scale-is-2-0-is-that-a-bug-is-it-correct-for-height">iOS 6 4-inch device screen scale is 2.0 is that a bug, is it correct for height?</a><li><a href="http://stackoverflow.com/questions/12507462/screen-height-for-iphone4-with-4-inch-screen">Screen height for iPhone4 with 4-inch screen</a></ul>zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-22704185972605510522012-09-18T21:35:00.001-07:002013-04-24T23:12:20.043-07:00Working around broken hiutil in Mountain Lion<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/working-around-broken-hiutil-in-mountain-lion/">here</a>. Please update your links.
<hr><br/>
For <a href="http://videobuffetapp.com">VideoBuffet</a>, I use <a href="http://flyingmeat.com/voodoopad/">VoodooPad</a> to author my help file. I learned about doing this from <a href="http://developer.casgrain.com/?p=16">this blog post</a>, and it's been working well for me. Until the last VideoBuffet update, when suddenly my help anchors stopped working.
<p>
As <a href="http://developer.casgrain.com/?p=16">the article states</a>, you basically make a Run Script build phase in Xcode which exports the VoodooPad doc as HTML. Then, your VoodooPad doc is setup to run Apple's help indexer (hiutil) to create the index. What the article doesn't tell you is how to open your help to a particular page.
<p>
To do this, in VoodooPad you make an alias to the page to which you want to link, and give it a name (in the title bar, click Info; then, under Aliases, click the + button and type the name). The screenshot below shows VideoBuffet's Preferences page, and you can see I've made an alias called "preferences".
<p>
<img src="http://lh3.ggpht.com/-jxiK7JTAXGo/UFlLrDyZDpI/AAAAAAAAADQ/kTdjinz3azQ/Voodoopad-example.png?imgmax=800" alt="VoodooPad screenshot" title="Voodoopad-example.png" border="0" width="600" height="447" />
<p>
Finally, in code (like, in the Help button in the Preferences dialog), you can do this:
<pre class="brush:c">
NSString *locBookName = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"];
[[NSHelpManager sharedHelpManager] openHelpAnchor:@"preferences" inBook:locBookName];
</pre>
<p>
…and the appropriate page will open up in Help Viewer. It works great, and I use this technique all over the place.
<p>
Back to the point. While testing VideoBuffet 1.0.3, I discovered that the help links were all broken. After a bit of hair-pulling I finally figured out that hiutil was giving me errors, and ending up not creating a proper index (it wasn't getting as far as my anchors). After Googling it, to no avail, I posted <a href="http://stackoverflow.com/questions/12473402/hiutil-not-indexing-properly-in-mountain-lion">a question on Stackoverflow</a>, which, at the time of this writing still has no responses. If you know the answer, by all means, post it there. :)
<p>
I've since discovered that the version of hiutil that ships with Mountain Lion (version 1.3) was where this problem started showing up, and the version from Lion (1.2) works as it always did. So, my (terrible) workaround: use the version of hiutil which shipped in Lion. Find yourself a Lion system, and copy /usr/bin/hiutil to the machine where you do your builds. I didn't want to replace the newer hiutil, so I called the Lion copy hiutil_old, and changed the VoodooPad export script to use that one. There's one other step: hiutil 1.2 depends on MPWXmlCore.framework, which no longer exists in Mountain Lion. So in order to use hiutil 1.2 on Mountain Lion, you need to copy MPWXmlCore.framework from a Lion system as well (it's in /System/Library/PrivateFrameworks/).
zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com3tag:blogger.com,1999:blog-2576103430982760618.post-53956715712361763822012-07-24T22:26:00.001-07:002013-04-24T23:12:28.577-07:00Sparrow's not a Canary<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/sparrows-not-a-canary/">here</a>. Please update your links.
<hr><br/>
<p>
All the cool kids are talking about <a href="http://www.tuaw.com/2012/07/20/sparrow-acquired-by-google-team-to-work-on-new-projects/">Sparrow's acquisition by Google</a>. People far smarter than I have chimed in about every aspect of the situation. But this caught my attention today:
<p>
<a href="http://appcubby.com/blog/the-sparrow-problem/">David Barnard, AppCubby</a>:
<blockquote>
...Sparrow is the proverbial canary in the coal mine. The age of selling software to users at a fixed, one-time price is coming to an end. It’s just not sustainable at the absurdly low prices users have come to expect. Sure, independent developers may scrap it out one app at a time, and some may even do quite well and be the exception to the rule, but I don’t think Sparrow would have sold-out if the team — and their investors — believed they could build a substantially profitable company on their own. The gold rush is well and truly over.
</blockquote>
<p>
It's true that the App Store has reset people's expectations with regard to the value of software, and I do believe that the race to the bottom is ultimately detrimental to indie developers. It is pretty apparent, based on what we know, that the Sparrow team didn't believe they could sustain a profitable business. But, how did we make the jump from "Sparrow could not sustain a profitable business building software" to "It's not possible to sustain a profitable business building software"?
<p>
I don't see how Sparrow's failure to build a sustainable business means that it's impossible to do so. Remember how <a href="http://arstechnica.com/apple/2012/05/ios-app-success-is-a-lottery-and-60-of-developers-dont-break-even/">60% of developers don't break even</a>?
<p>
Sparrow had a 5 person development team, and was selling a $3 app. Read that again, because it's kind of important. If you have a 5 person dev team, and you're selling a $3 app, you probably plan on selling a whole lot of apps. Between Macs and iOS devices, there are over 200 million potential devices that can run Sparrow. And every single one of them ships with a very good email client. Are you seeing the problem yet?
<p>
I believe Sparrow's failure to build a sustainable business can be attributed to:
<ol>
<li>Overestimating the market for email client software.</li>
<li>Underestimating the value of their software to it's actual target market.</li>
</ol>
<p>
I consider myself a nerd. I have a full-time job as a software developer, and I've got <a href="http://www.fat-apps.com">my own indie dev business going on the side</a>. I have six (count 'em) active email accounts, and regularly send and receive many dozens of emails per day. But I don't own Sparrow. Why? Apple's Mail is good enough for me. Sparrow solves a problem I don't have.
<p>
I don't think I'm alone. Mail is actually a pretty good app. I don't think it even occurs to most people to go looking for a better email app, just as it doesn't occur to most people to go looking for a better web browser.
<p>
The Twittersphere exploded in a shitstorm of angry sentiment from butt-hurt Sparrow users lamenting the ultimate demise of their software. Clearly, this was a very important piece of software for those that did buy it. I'm willing to bet that most of the people that paid $3 (or $10) for Sparrow would gladly have paid much more.
<p>
<a href="http://inessential.com/2010/01/16/email_init">Brent Simmons knew this, years ago</a>:
<blockquote>
I’ve been joking for years that I’m going to write an email client and charge $500 for it — an email client that actually meets the needs of developers and professionals who rely on email, folks who type for a living.
</blockquote>
<p>
I read somewhere that this may have been the genesis of the idea for Sparrow. I hope not, because if it was, they clearly didn't read the next paragraph:
<blockquote>
But I’m not going to, and I don’t know anybody who is. The economics of it make it kind of tough, given that Apple ships a good email client with OS X.
</blockquote>
<p>
Sparrow was a premium product, sold at a commodity price. If BMW started selling their cars for $15,000, I don't think it would surprise anyone when they lost buckets of money, and I don't think anyone would declare that it's not possible to sustain a profitable business selling cars.
<p>
<a href="http://www.marco.org/2012/07/21/followup-talent-acquisitions">Marco Arment nailed it:</a>
<blockquote>
Don’t blame Sparrow. Blame the terrible market for email clients.
</blockquote>
<p>
Just because most apps are $0.99, doesn't mean yours has to be. What is your software worth to your customers? <a href="http://sethgodin.typepad.com/seths_blog/2011/09/the-forever-recession.html">Seth Godin</a> said, "The problem with the race to the bottom is that you might win…"
zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-13311776764274995472012-07-21T13:13:00.001-07:002013-04-24T23:12:37.420-07:00High resolution timing in Cocoa<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/high-resolution-timing-in-cocoa/">here</a>. Please update your links.
<hr><br/>
For profiling app performance, it's necessary to accurately time your code. The best way to do this is to use mach_absolute_time. In conjunction with mach_timebase_info, you can get extremely high resolution timing to ease performance benchmarking.
<p>
I've wrapped this functionality up in a little class to make it super easy to use.
<pre class="brush:c">
// MachTimer.h
#include <mach/mach_time.h>
@interface MachTimer : NSObject
{
uint64_t timeZero;
}
+ (id) timer;
- (void) start;
- (uint64_t) elapsed;
- (float) elapsedSeconds;
@end
</pre>
<pre class="brush:c">
// MachTimer.m
#import "MachTimer.h"
static mach_timebase_info_data_t timeBase;
@implementation MachTimer
+ (void) initialize
{
(void) mach_timebase_info( &timeBase );
}
+ (id) timer
{
#if( __has_feature( objc_arc ) )
return [[[self class] alloc] init];
#else
return [[[[self class] alloc] init] autorelease];
#endif
}
- (id) init
{
if( (self = [super init]) ) {
timeZero = mach_absolute_time();
}
return self;
}
- (void) start
{
timeZero = mach_absolute_time();
}
- (uint64_t) elapsed
{
return mach_absolute_time() - timeZero;
}
- (float) elapsedSeconds
{
return ((float)(mach_absolute_time() - timeZero))
* ((float)timeBase.numer) / ((float)timeBase.denom) / 1000000000.0f;
}
@end
</pre>
You'd use it like this:
<pre class="brush:c">
MachTimer* aTimer = [MachTimer timer];
[self performSomeOperation];
NSLog( @"performSomeOperation took %f seconds", [aTimer elapsedSeconds] );
</pre>
I've used this code for iOS and Mac OS apps, and it works great.
zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-26307911365236808462012-07-19T00:52:00.001-07:002013-04-24T23:12:44.873-07:00Retina Graphics in Mac OS X<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/retina-graphics-in-mac-os-x/">here</a>. Please update your links.
<hr><br/>
<p>I recently added Retina graphics to the Mac version of <a href="http://fat-apps.com/apps/puzzletiles-mac.html">PuzzleTiles</a>. I had Retina-ized <a href="http://fat-apps.com/apps/tasklog.html">TaskLog</a> prior, and found that to be pretty trivial, as it's not a very graphics-heavy app. <a href="http://fat-apps.com/apps/puzzletiles-mac.html">PuzzleTiles</a> has many hundreds of images, so it was a bit more of a challenge. <p>The whole process is pretty straightforward, especially if you've done Retina graphics for an iOS app. The <a href="http://www.urbandictionary.com/define.php?term=tl%3Bdr">TL;DR</a> is: <ul><li>Make double-size images and suffix them with @2x</li><li>Use NSImage imageNamed: to load them</li><li>Use Quartz Debug to test your new graphics (if you don't have a Retina MacBook Pro)</li></ul><strong>The code</strong><p>The main thing is to use <pre>NSImage imageNamed:</pre> It does all the @2x image-loading magic. One thing not immediately obvious to me is that you <em>don't</em> specify the extension when doing so. <br /><pre class="brush:c"><br />NSImage* myImage = [NSImage imageNamed:@"foo.png"];<br />// NO, this makes the magic not work.<br /></pre><pre class="brush:c"><br />NSImage* myImage = [NSImage imageNamed:@"foo"];<br />// YES, magic is a-comin'.<br /></pre><strong>The art</strong><p><br />Producing 2x artwork was, by far, the biggest task. Luckily we'd had the foresight to make all our source art with vector graphics (text, shapes, and effects layers) which are basically arbitrarily resizable. <p>The first step was batch upsizing everything to 2x. I ended up making a separate set of source art for our @2x images, so that we could tweak them by hand. Some of the artwork we upsized with no other changes, but for some of them we wanted to tweak some things: line or shadow thickness, for example. Some others (the Wood tile set, for example) had bitmaps which we had to completely redo. <br /><br /><p>My advice is this: if your source art isn't vector-based, it might be time to bite the bullet and do that. <p>If you're like me and don't have a graphic artist on staff to do all this stuff for you, I have a few pieces of advice to ease the Retina-izing process: <br /><br /><ul><li>Get familiar with PhotoShop's automation tools (File->Automate->Batch and File->Scripts->Image Processor). This saved me tons of time when converting, resizing, and exporting hundreds of images at once.</li><li>Get a tool for batch file renaming. At one point I decided to change my file naming convention (to make things more amenable to using imageNamed:), and hand renaming nearly a thousand files would have been a time-consuming, error-prone task. I ended up using <a href="http://itunes.apple.com/us/app/core-renamer/id416085657?mt=12">Core-Renamer</a> for this, and it worked perfectly.</li></ul><strong>Testing</strong><p>If you don't have a Retina MacBook Pro, you can use any old Mac to test out your Retina graphics. Download and install Graphics Tools for Xcode (from within Xcode, choose Xcode->Open Developer Tool->More Developer Tools). Run Quartz Debug, choose Window->UI Resolution, and check "Enable HiDPI display modes". Now, when you go to Displays in System Preferences, you'll see a bunch of HiDPI modes. Pick one, run your app, and see how it looks. <br /><br /><p><strong>References</strong><p><a href="https://developer.apple.com/videos/wwdc/2012/?id=213">The WWDC 2012 session "Introduction to High Resolution on OS X"</a> is fantastic; I highly recommend checking that out. <br />zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-65537044424642246132012-06-21T01:19:00.001-07:002013-04-24T23:12:52.111-07:00Autolayout: Don't Fight It<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/autolayout-dont-fight-it/">here</a>. Please update your links.
<hr><br/>
<a href="http://TaskLogApp.com">TaskLog</a> is the first app I've developed which uses the new <a href="https://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/Introduction.html">Cocoa Autolayout</a> introduced in Mac OS 10.7 Lion.
<p>
I first used it in TaskLog's log view, shown below:
<img src="http://lh5.ggpht.com/-329147ZuSIU/T-LZCu-4vHI/AAAAAAAAAC8/Av2te_aL-58/Task%252520Log%252520screenshot.png?imgmax=800" alt="Task Log screenshot" title="Task Log screenshot.png" border="0" width="600" height="338" />
<p>
As you can see, there are quite a few UI elements in there. But all I did was put them where I wanted them, and Autolayout figured it all out. I didn't have to configure a single thing, it all Just Worked™.
<p>
Now at some point I decided to make TaskLog's main view resizable (so that the entire window resizes to perfectly fit the text of the current task). And that's where Autolayout became a real headache. In this view, there are actually a lot of overlapping UI elements which are shown or hidden depending on what the state of the UI. For example, when you hit "Start a new task", a bunch of the UI is hidden, and a text entry field and Start/Cancel buttons are displayed.
<img src="http://lh4.ggpht.com/-sOe0kokYwFE/T-LTyFDhc2I/AAAAAAAAACw/2VcRuQMhrAo/TaskLog%252520main%252520view%252520screenshot2.png?imgmax=800" alt="TaskLog main view screenshot 2" title="TaskLog main view screenshot2.png" border="0" width="398" height="260" />
<p>
Then when you hit Start, those are hidden and a bunch of other views are displayed.
<img src="http://lh5.ggpht.com/-ouilaMKggeg/T-LZC-WzvYI/AAAAAAAAADE/_d-_cprTZZY/TaskLog%252520Main%252520window%252520screenshot.png?imgmax=800" alt="TaskLog Main window screenshot" title="TaskLog Main window screenshot.png" border="0" width="398" height="260" />
<p>
I wanted some of the views to be anchored to the bottom of the window, some to be anchored to the top, and a few to be anchored to both top and bottom and resize vertically. More importantly, Autolayout was having none of this. I spent an entire day adding my own constraints to try to get the behavior I was after, and in the end I wasn't able to get it to work that way I wanted.
<p>
Then I had an epiphany: <em>give up</em>. It turns out that springs and struts can totally do what I needed to do in the main view UI; there was no compelling reason to use Autolayout. I turned it off, set up some springs and struts, and went on my merry way.
<p>
Now, don't get me wrong, Autolayout is amazing. It allows you to do incredibly complicated layout with little to no effort. However, my advice is this: if you find yourself fighting Autolayout, stop and think about what you're trying to do. If what you're trying to accomplish can be done with springs/struts, and/or Autolayout isn't making your life better, simply turn it off for that view.
<p>
The <a href="https://developer.apple.com/videos/wwdc/2011/includes/cocoa-autolayout.html#cocoa-autolayout">Autolayout session from last year's WWDC</a> is an awesome intro to Autolayout. I'd definitely recommend giving that a view if you haven't already.
zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-35473968442883998922012-02-14T20:18:00.001-08:002013-04-24T23:12:59.403-07:00VideoBuffet 1.0.2 released on the Mac App Store<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/videobuffet-1-0-2-released-on-the-mac-app-store/">here</a>. Please update your links.
<hr><br/>
This is a minor update, basically bug fixes. However, we did take the opportunity to convert it to Automatic Reference Counting (ARC). It really was as easy as it looked in the <a href="https://developer.apple.com/videos/wwdc/2011/#introducing-automatic-reference-counting">ARC WWDC session</a>.
<p>
I highly recommend watching that video if you're considering moving to ARC, as well as reading the <a href="https://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html#//apple_ref/doc/uid/TP40011226">Transitioning to ARC Release Notes</a>, and the <a href="http://clang.llvm.org/docs/AutomaticReferenceCounting.html">llvm ARC page</a>. Bookmark that last page, because though it might be rough reading all the way through, it's a very handy reference.
<p>
I had planned on posting some tips and tricks regarding converting to ARC, but truthfully, I don't have any. It was simple, and pretty much just worked.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-10817672238509503652012-01-28T11:59:00.001-08:002013-04-24T23:13:08.891-07:00PuzzleTiles for Mac 1.1<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/puzzletiles-for-mac-1-1/">here</a>. Please update your links.
<hr><br/>
PuzzleTiles for Mac 1.1 is now available on the Mac App Store. Check out the <a href="http://fat-apps.com/apps/puzzletiles-mac">app page</a>, or <a href="http://itunes.apple.com/us/app/puzzletiles/id406892090?mt=12">buy it now</a>.
zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-53737776036293711262012-01-20T21:45:00.001-08:002013-04-24T23:13:15.887-07:00VideoBuffet 1.0.1 released<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/videobuffet-1-0-1-released/">here</a>. Please update your links.
<hr><br/>
<a href="http://videobuffetapp.com">VideoBuffet</a> 1.0.1 is now available in the <a href="http://itunes.apple.com/us/app/videobuffet/id486622878?mt=12">Mac App Store</a>. It's a minor update, which adds one new feature, and fixes several annoying bugs. <a href="http://www.fat-apps.com/videobuffet-release-notes">Release Notes are here</a>.
If you haven't checked out VideoBuffet yet, do so. We think it's great.
zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-18060138006881938782011-12-15T23:47:00.000-08:002013-04-24T23:13:25.075-07:00VideoBuffet now available<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/videobuffet-now-available/">here</a>. Please update your links.
<hr><br/>
<shameless plug=""></shameless><br />
<shameless plug><br />
VideoBuffet, Fat Apps' QuickTime movie browser and player, is now available on the Mac App Store. <a href="http://bit.ly/vOFYxE">Check it out</a>, or <a href="http://itunes.apple.com/us/app/videobuffet/id486622878?ls=1&mt=12">buy it now</a>. <br />
<shameless plug><br />
<shameless plug=""></shameless>zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-76274210464762695422011-11-16T10:41:00.001-08:002013-04-24T23:13:32.301-07:00Don't invalidate your NSTimer in dealloc<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/dont-invalidate-your-nstimer-in-dealloc/">here</a>. Please update your links.
<hr><br/>
Seasoned Cocoa developers may snicker at the title of this post, because it's probably obvious to them. It should have been obvious to me, but it wasn't. Maybe I can save someone some head-scratching by relaying my tale.<br /><br />NSTimers retain their target. Now, I've been around the Cocoa block a few times, so I knew this to be the case (<a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nstimer_Class/Reference/NSTimer.html#//apple_ref/occ/clm/NSTimer/scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:">the docs</a> state it explicitly), but I clearly hadn't thought through the ramifications. Walk with me for a minute.<br /><br />Let's say you have a window, MainWindow, and a controller class for it, MainWindowController. You put a timer on it to periodically do some stuff. It might look like this:<br /><br /><pre class="brush:c"><br />@interface MainWindowController : NSWindowController<br />{<br /> NSTimer* myTimer;<br />}<br />- (void) doSomeStuff;<br />@end<br /><br />@implementation MainWindowController<br /><br />- (id) initWithWindow:(NSWindow*)window<br />{<br /> self = [super initWithWindow:window];<br /> if( self ) {<br /> const NSTimeInterval timerInterval = 10.0f;<br /> myTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval<br /> target:self<br /> selector:@selector(doSomeStuff)<br /> userInfo:nil<br /> repeats:YES];<br /> }<br /> return self;<br />}<br /><br />- (void) dealloc<br />{<br /> [myTimer invalidate], myTimer = nil;<br /> [super dealloc];<br />}<br /><br />- (void) doSomeStuff<br />{<br /> NSLog( @"doing stuff" );<br />}<br /><br />@end<br /></pre><br /><br />Seems reasonable, no? It did to me. The problem is that, as <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nstimer_Class/Reference/NSTimer.html#//apple_ref/occ/clm/NSTimer/scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:">the docs</a> state, NSTimers retain their target. When you create that timer in initWithWindow:, it retains the window controller, which means <em>dealloc will never be called</em>. dealloc isn't called until the controller's retain count is zero, and until the timer invalidates (which will be never, on a repeating timer), the controller's retain count will never be zero.<br /><br />The solution would be to invalidate the timer elsewhere, perhaps in windowWillClose: (of course, your controller must also be the window's delegate for that to happen). <br /><br /><pre class="brush:c"><br />- (void) windowWillClose:(NSNotification*)notification<br />{<br /> [myTimer invalidate], myTimer = nil;<br />}<br /></pre><br /><br />Another thing I'd like to mention is the use of retainCount for debugging purposes. I've found that brand new Cocoa programmers tend to rely way too much on retainCount to try to figure out their memory management issues. Cocoa pros, on the other hand, will tell you to never ever call retainCount. You can't get any useful information from it, some say, because you can't know who's retaining your objects.<br /><br />I think the truth lies somewhere in between. The truth is, you <em>should</em> know who's retaining your objects, and why. Though retainCount shouldn't be the first thing you look to, it can be useful on occasion if it seems like things aren't working as you expect.<br /><br />In my case, I observed that calling initWithWindowNibName: on my window controller was returning an object with a retainCount of 2, when I was expecting 1. From there it was a pretty short walk to get to "ok, my timer is created here, but it's invalidated in dealloc… oh, wait."<br />zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-79251682583465081982011-09-29T09:16:00.001-07:002013-04-24T23:13:38.758-07:00Regarding first responders: make, don't become<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/regarding-first-responders-make-dont-become/">here</a>. Please update your links.
<hr><br/>
Things That Were Not Immediately Obvious To Me, #27:<br /><br />If you have a view which you wish to become first responder, do <em>NOT</em> call becomeFirstResponder on it; it doesn't actually make the view first responder. Instead, call NSWindow's makeFirstResponder:<br /><br />The <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html">NSResponder doc</a> says (emphasis mine):<br /><blockquote><br />Use the NSWindow makeFirstResponder: method, not this method, to make an object the first responder. <em>Never invoke this method directly.</em></blockquote><br /><br />This was not immediately obvious to me. Moral of the story: always read the damn docs.<br /><br />Recapping:<br /><pre class="brush:c"><br />[self becomeFirstResponder]; // Nope, never do that.<br /><br />[[self window] makeFirstResponder:self]; // That'll do, pig.<br /></pre><br /><br />As a side note, the <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html#//apple_ref/occ/instm/NSResponder/becomeFirstResponder">description of becomeFirstResponder</a> says:<br /><blockquote><br />Notifies the receiver that it’s about to become first responder in its NSWindow.<br /></blockquote><br />and<br /><blockquote><br />The default implementation returns YES, accepting first responder status. Subclasses can override this method to update state or perform some action such as highlighting the selection, or to return NO, refusing first responder status.<br /></blockquote><br /><br />OK, so can someone tell me why this method wasn't named shouldBecomeFirstResponder? Had that been the case, I wouldn't have had to resort to the docs to figure out why it wasn't doing what I thought it should do. Just sayin'.<br /><br /><br />zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-34379385026349476842011-08-16T19:37:00.001-07:002013-04-24T23:13:46.173-07:00PuzzleTiles on sale<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/puzzletiles-on-sale/">here</a>. Please update your links.
<hr><br/>
<p><shameless plug></p><p>For the next 48 hours, Fat Apps is giving away <a href="http://itunes.apple.com/us/app/puzzletiles/id387595084?mt=8">PuzzleTiles</a>, <a href="http://itunes.apple.com/us/app/puzzletiles-hd/id425981552?mt=8">PuzzleTiles HD</a>, and <a href="http://itunes.apple.com/us/app/puzzletiles/id406892090?mt=12">PuzzleTiles/Mac</a> for free. Grab 'em while you can!</p><p></shameless plug></p><p> </p>zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-51679373509471135552011-07-28T22:20:00.001-07:002013-04-24T23:13:52.862-07:00Adventures in Redirection III: The Revenge<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/adventures-in-redirection-iii-the-revenge/">here</a>. Please update your links.
<hr><br/>
After the last two entries on output redirection (<a href="http://bit.ly/lCamPM">here</a> and <a href="http://bit.ly/qILIJc">here</a>, if you missed them), a few folks said, “Zach, this is a Cocoa blog; what’s up with all the crazy C++ junk?” Fair enough. The main reason is that this was what I needed for the particular project I’m working on. Another reason is that C++ is what I did (a *lot*) in my previous life, so hopefully you’ll forgive me if I occasionally run home to mama. :) <br />
<br />
In any case, the core code is plain ole C, so I thought, why not make a Cocoa version also? So here we go. The walkthrough is basically the same as <a href="http://bit.ly/qILIJc">the original C++ version</a>, so I won't repeat it here.<br />
<br />
<pre class="brush:c">@interface StandardOutputRedirector : NSObject
{
int redirectionPipe[2];
int oldStandardOutput;
int oldStandardError;
BOOL redirecting;
NSMutableString* redirectedOutput;
}
- (void) startRedirecting;
- (void) stopRedirecting;
- (NSString*) output;
- (void) clearOutput;
@end
@implementation StandardOutputRedirector
enum { READ, WRITE };
#pragma mark - Memory Management
- (id) init
{
if( (self = [super init]) ) {
redirectedOutput = [[NSMutableString alloc] init];
if( pipe( redirectionPipe ) != -1 ) {
oldStandardOutput = dup( fileno(stdout) );
oldStandardError = dup( fileno(stderr) );
}
setbuf( stdout, NULL );
setbuf( stderr, NULL );
}
return self;
}
- (void) dealloc
{
if( redirecting ) {
[self stopRedirecting];
}
if( oldStandardOutput > 0 ) {
close( oldStandardOutput );
}
if( oldStandardError > 0 ) {
close( oldStandardError );
}
if( redirectionPipe[READ] > 0 ) {
close( redirectionPipe[READ] );
}
if( redirectionPipe[WRITE] > 0 ) {
close( redirectionPipe[WRITE] );
}
[redirectedOutput release];
[super dealloc];
}
#pragma mark - Public methods
- (void) startRedirecting
{
if( redirecting ) return;
dup2( redirectionPipe[WRITE], fileno(stdout) );
dup2( redirectionPipe[WRITE], fileno(stderr) );
redirecting = true;
}
- (void) stopRedirecting
{
if( !redirecting ) return;
dup2( oldStandardOutput, fileno(stdout) );
dup2( oldStandardError, fileno(stderr) );
redirecting = false;
}
- (NSString*) output
{
const size_t bufferSize = 4096;
char buffer[bufferSize];
fcntl( redirectionPipe[READ], F_SETFL, O_NONBLOCK );
ssize_t bytesRead = read( redirectionPipe[READ], buffer, bufferSize - 1 );
while( bytesRead > 0 ) {
buffer[bytesRead] = 0;
NSString* tempString = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
[redirectedOutput appendString:tempString];
bytesRead = read( redirectionPipe[READ], buffer, bufferSize );
}
return [NSString stringWithFormat:@"%@", redirectedOutput];
}
- (void) clearOutput
{
[redirectedOutput setString:@""];
}
@end
</pre><br />
<br />
One thing I would note on the Cocoa version is that, this is intended for Mac OS X. I’ve not tried it on iOS, I’ve no idea if it would work, and I actually can’t think of a reason why you’d want to do it on iOS.<br />
<br />
This Cocoa version (along with the original C++ implementation) is included with the sample project on github, <a href="https://github.com/zpasternack/Redirector">here</a>.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-42367682437706835032011-07-23T21:55:00.000-07:002013-04-24T23:13:59.617-07:00ZPAlertView, Redirector on github<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/zpalertview-redirector-on-github/">here</a>. Please update your links.
<hr><br/>
I finally took a minute to put <a href="https://github.com/zpasternack/ZPAlertViewTest">ZPAlertView</a> and <a href="https://github.com/zpasternack/Redirector">Redirector</a> up on github.<br />
<br />
The related articles are <a href="http://zpasternack.blogspot.com/2011/02/uialertview-with-blocks-revisited.html">UIAlertView with Blocks, Revisited</a> and <a href="http://zpasternack.blogspot.com/2011/07/adventures-in-redirection-part-deux.html">Adventures in Redirection, Part Deux</a><br />
<br />
If you find either one useful, drop me a line and let me know.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-36872173841922909342011-07-23T00:25:00.001-07:002013-04-24T23:14:06.716-07:00Adventures in Redirection, Part Deux<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/adventures-in-redirection-part-deux/">here</a>. Please update your links.
<hr><br/>
In the <a href="http://zpasternack.blogspot.com/2011/06/stdout-and-stderr-adventures-in.html">previous chapter</a>, I showed how to redirect cout and cerr, and how it turns out that doing so does not also redirect stdout and stderr. As this turned out not to meet my requirements, I went in search of other methods.<br />
In Googling around, the most common way to redirect stdout/stderr involves calling freopen on them. However, this didn’t work for me, for a few reasons. For one thing, freopen is really geared toward redirecting to a file. You can engage in a bit of trickery to redirect to memory instead, but I wasn’t really interested in going down that path. Worse, it’s completely irreversible; there’s no portable way to restore stdout/stderr once you redirect them via freopen. The ability to arbitrarily redirect and restore output was a must-have for me, so freopen couldn't cut it.<br />
I finally settled on a method which involves calling dup2 to duplicate the stdout/stderr file descriptors. In this way, you can redirect to one end of a pipe, then read from the other end whenever it's convenient. More importantly, you can save off the old file descriptors so that you can restore them.<br />
Let’s get right to it. You might remember the IOutputRedirector interface from <a href="http://zpasternack.blogspot.com/2011/06/stdout-and-stderr-adventures-in.html">last time</a>:<br />
<pre class="brush:c">class IOutputRedirector
{
public:
IOutputRedirector() {}
virtual ~IOutputRedirector(){}
virtual void StartRedirecting() = 0;
virtual void StopRedirecting() = 0;
virtual std::string GetOutput() = 0;
virtual void ClearOutput() = 0;
};</pre>
For our new class, we just need a couple of ints to hold our pipe file numbers, a couple of ints for the saved stdout/stderr file numbers, a bool to indicate that we’re redirecting, and a std::string which we redirect into.<br />
<pre class="brush:c">class CStdoutRedirector : public IOutputRedirector
{
public:
CStdoutRedirector();
virtual ~CStdoutRedirector();
virtual void StartRedirecting();
virtual void StopRedirecting();
virtual std::string GetOutput();
virtual void ClearOutput();
private:
int _pipe[2];
int _oldStdOut;
int _oldStdErr;
bool _redirecting;
std::string _redirectedOutput;
};</pre>
In the constructor we create a pipe, and call dup to copy the stdout and stderr file descriptors, so that we can restore them later. The setbuf calls are there to disable buffering. stdout is typically line-buffered whilst stderr is typically unbuffered, resulting in the combined output not being properly interleaved otherwise.<br />
<pre class="brush:c">CStdoutRedirector::CStdoutRedirector()
: IOutputRedirector()
, _oldStdOut( 0 )
, _oldStdErr( 0 )
, _redirecting( false )
{
_pipe[READ] = 0;
_pipe[WRITE] = 0;
if( pipe( _pipe ) != -1 ) {
_oldStdOut = dup( fileno(stdout) );
_oldStdErr = dup( fileno(stderr) );
}
setbuf( stdout, NULL );
setbuf( stderr, NULL );
}</pre>
The destructor does some cleanup (stopping the redirection and closing files).<br />
<pre class="brush:c">CStdoutRedirector::~CStdoutRedirector()
{
if( _redirecting ) {
StopRedirecting();
}
if( _oldStdOut > 0 ) {
close( _oldStdOut );
}
if( _oldStdErr > 0 ) {
close( _oldStdErr );
}
if( _pipe[READ] > 0 ) {
close( _pipe[READ] );
}
if( _pipe[WRITE] > 0 ) {
close( _pipe[WRITE] );
}
}</pre>
StartRedirecting is where part of the magic is. We call dup2 to copy the stdout and stderr file descriptors to the write end of our pipe. This is how the redirection works: after the dup2 calls, any writes to stdout/stderr go instead to the write end of our pipe.<br />
<pre class="brush:c">void
CStdoutRedirector::StartRedirecting()
{
if( _redirecting ) return;
dup2( _pipe[WRITE], fileno(stdout) );
dup2( _pipe[WRITE], fileno(stderr) );
_redirecting = true;
}</pre>
StopRedirecting copies the stdout and stderr file descriptors back to the ones we saved in the constructor, resulting in restoring stdout/stderr.<br />
<pre class="brush:c">void
CStdoutRedirector::StopRedirecting()
{
if( !_redirecting ) return;
dup2( _oldStdOut, fileno(stdout) );
dup2( _oldStdErr, fileno(stderr) );
_redirecting = false;
}</pre>
GetOutput is where the rest of the magic lies. We call fcntl to set the read end of our pipe to nonblocking mode (otherwise the read call will block if the pipe is empty). Then we call read in a loop into a little temporary buffer, NULL terminate it, and append it to a string, which we then return.<br />
<pre class="brush:c">string
CStdoutRedirector::GetOutput()
{
const size_t bufSize = 4096;
char buf[bufSize];
fcntl( _pipe[READ], F_SETFL, O_NONBLOCK );
ssize_t bytesRead = read( _pipe[READ], buf, bufSize - 1 );
while( bytesRead > 0 ) {
buf[bytesRead] = 0;
_redirectedOutput += buf;
bytesRead = read( _pipe[READ], buf, bufSize );
}
return _redirectedOutput;
}</pre>
ClearOutput simply clears out the string.<br />
<pre class="brush:c">void
CStdoutRedirector::ClearOutput()
{
_redirectedOutput.clear();
}</pre>
There is one caveat to this method: if the internal buffer fills up, subsequent output (printf, for example) will block until space is freed up in the buffer. This buffer appears to be 16k in size, though I imagine it varies depending on OS. So basically, if you intend to redirect more than 16k at once. well... that won’t work. Calling GetOutput clears the buffer out, so call it often, if you can.<br />
I was thinking that supplying my own larger buffer via setvbuf() would help, but it turns out not to. If anyone has any ideas on how to make this better, let me know.<br />
I would also point out that I've not used this in production code (I am using it in the project I'm working on now, and it seems to be working perfectly; we just haven't shipped it yet).<br />
I'll put the full source and sample project up on github when I have a spare few minutes.<br />
<strong>Update</strong>: Full source and sample project are on github, <a href="https://github.com/zpasternack/Redirector">here</a>.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-28617520029992411872011-06-15T21:18:00.000-07:002013-04-24T23:14:15.616-07:00The rest of us need to be smarter<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/the-rest-of-us-need-to-be-smarter/">here</a>. Please update your links.
<hr><br/>
<a href="http://www.leavesofcode.com/2011/06/we-need-programming-language-for-rest.html">This</a> made me simultaneously amused and angered. There’s probably nothing more to be said that hasn’t already been so in comments there and on <a href="http://www.reddit.com/r/programming/comments/hz87n/journalist_writes_programing_is_too_hard_wishes/">Reddit</a>. Still, I can’t help but put my $0.02 in.<br />
<br />
I don’t know anything about construction, except that it’s hard. To build a house, you need to pour concrete, nail boards together, put up drywall, and a bunch of other things. That’s all hard. If only I had a robot that could do all those things for me... I still couldn’t build a house. Why? Because I also don’t know anything about architecture or structural engineering, or plumbing, or electrical engineering. I’d be lucky to build something that didn’t fall down, and it almost certainly wouldn’t be fit for habitation.<br />
<br />
When people say “programming”, they think of the mechanical act of typing stuff into a computer. But that’s really only a tiny fraction of what a programmer does. If you don’t have a reasonable understanding of the innerworkings of computers; if you don’t have the capacity to break a large problem down into ever smaller problems; if you don’t have the ability to visualize every minute detail of a solution; no programming language will enable you to write decent software. That’s all there is to it.<br />
<br />
Do you guys remember <a href="http://en.wikipedia.org/wiki/HyperCard">HyperCard</a>? <a href="http://developer.apple.com/library/mac/#documentation/AppleScript/Conceptual/AppleScriptX/AppleScriptX.html">AppleScript</a>? <a href="http://www.mactech.com/articles/mactech/Vol.10/10.11/PrographCPXTutorial/index.html">Prograph CPX</a>? Dare I say it, <a href="http://en.wikipedia.org/wiki/Visual_Basic">Visual BASIC</a>? All attempts to make a programming language for non-programmers. And every time one of these hot new “languages for the rest of us” come out, I polish up my résumé. Because once everyone realizes that’s programming is still hard, I get a bunch of new job offers. Best case, all it means is we’re momentarily awash in software written by people who have no business doing so.<br />
<br />
That’s not me being elitist. I <em>want</em> to live in a world where everyone has the capacity to create great software, I <em>truly</em> do. I just don’t see it happening in my lifetime.<br />
<br />
<span style="font-size: 18pt;">> those who know it have little interest in simplifying it as it devalues their own knowledge.</span><br />
<br />
Bitch, please. The guy that’s able to engineer a programming language which enables non-programmers to create great software will instantly become ludicrously rich, not to mention ushering in a golden era of information technology. If you believe for one second that some of the smartest people on the planet aren’t working on this right now -- haven’t been working on this for decades -- you are sorely mistaken.<br />
<br />
Do you have any musician friends? Go up to one and tell him you tried to play guitar today, and it was hard. Why don’t they make instruments that are easier to play? Let me know how that works out for you.<br />
<br />
Any four-year-old of even average intellect has sufficient command of their native language to successfully convey any idea of which they conceive. That no four-year-old has ever won the Pulitzer Prize is proof of the failure of the English language. Clearly what is needed is an easier to use spoken language.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com2tag:blogger.com,1999:blog-2576103430982760618.post-63996903859524924692011-06-04T13:41:00.000-07:002013-04-24T23:14:22.876-07:00stdout and stderr: Adventures in Redirection, Part 1<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/stdout-and-stderr-adventures-in-redirection-part-1/">here</a>. Please update your links.
<hr><br/>
For a project I’m working on, I needed to redirect the standard output and standard error of a subprocess I’m launching. While it’s trivial to do this with NSTask (just send it setStandardOutput: and setStandardError:), I had some unique requirements that precluded this. Mainly, I didn’t want the process’ stdout/stderr redirected for its entire lifespan. Rather, I wanted it to redirect only certain parts, and return that data to the GUI through a separate mechanism (a named pipe, actually).<br />
<br />
So, what I wanted was to be able to was:<br />
1. Enable/disable the redirection at any point, while the process is running (this more or less precludes freopen- based solutions, as there’s no portable way to restore the streams once they’re reopened).<br />
2. Be able to retrieve and clear the redirected output at any time, regardless of whether redirection is enabled or not.<br />
3. stdout and stderr must both be redirected to the same buffer, and properly interleaved.<br />
4. The helper app (the app that has this code) is written in C++; no Cocoa, no Objective-C.<br />
<br />
Based on those requirements, I wrote the following unit test to determine what my interface should look like.<br />
<br />
<pre class="brush: c">int RedirectorTest()
{
IOutputRedirector theRedirector;
std::cout << "This is not redirected" << std::endl;
theRedirector.StartRedirecting();
std::cout << "This is redirected";
assert( theRedirector.GetOutput() == "This is redirected"
&& "cout not redirected!" );
theRedirector.ClearOutput();
assert( theRedirector.GetOutput().length() == 0
&& "redirector not cleared" );
std::cerr << "Redirected Error";
assert( theRedirector.GetOutput() == "Redirected Error"
&& "cerr not redirected" );
theRedirector.ClearOutput();
assert( theRedirector.GetOutput().length() == 0
&& "redirector not cleared" );
theRedirector.StopRedirecting();
std::cout << "This is also not redirected";
assert( theRedirector.GetOutput().length() == 0
&& "redirection not stopped!" );
theRedirector.StartRedirecting();
std::cout << "cout";
std::cerr << "cerr";
std::cout << "cout again";
std::string output = theRedirector.GetOutput();
assert( theRedirector.GetOutput() == "coutcerrcout again"
&& "cout, err not interleaved properly" );
return 0;
}
</pre>
Based on that test I wrote the following interface I’d use for my test class:
<pre class="brush: c">class IOutputRedirector
{
public:
IOutputRedirector() {}
virtual ~IOutputRedirector(){}
virtual void StartRedirecting() = 0;
virtual void StopRedirecting() = 0;
virtual std::string GetOutput() = 0;
virtual void ClearOutput() = 0;
};
</pre>I Googled around for various techniques on how to do this, and the most promising one involved calling rdbuf on the cout and cerr streams. In this way, you can specify a different streambuf, and I use that of an ostringstream. It looks a little somethin’ like this:
<pre class="brush: c">class CCoutRedirector : public IOutputRedirector
{
public:
CCoutRedirector();
virtual ~CCoutRedirector();
virtual void StartRedirecting();
virtual void StopRedirecting();
virtual std::string GetOutput();
virtual void ClearOutput();
private:
std::streambuf* m_pOldCoutStreamBuf;
std::streambuf* m_pOldCerrStreamBuf;
std::ostringstream m_stream;
};
CCoutRedirector::CCoutRedirector()
: IOutputRedirector()
, m_pOldCoutStreamBuf( NULL )
, m_pOldCerrStreamBuf( NULL )
{
}
CCoutRedirector::~CCoutRedirector()
{
StopRedirecting();
}
void
CCoutRedirector::StartRedirecting()
{
if( m_pOldCoutStreamBuf != NULL ||
m_pOldCerrStreamBuf != NULL )
{
return;
}
m_pOldCoutStreamBuf = std::cout.rdbuf();
m_pOldCerrStreamBuf = std::cerr.rdbuf();
streambuf* newOutbuf = m_stream.rdbuf();
cout.rdbuf( newOutbuf );
cerr.rdbuf( newOutbuf );
}
void
CCoutRedirector::StopRedirecting()
{
if( m_pOldCoutStreamBuf != NULL )
{
cout.rdbuf( m_pOldCoutStreamBuf );
m_pOldCoutStreamBuf = NULL;
}
if( m_pOldCerrStreamBuf != NULL )
{
cerr.rdbuf( m_pOldCerrStreamBuf );
m_pOldCerrStreamBuf = NULL;
}
}
string
CCoutRedirector::GetOutput()
{
return m_stream.str();
}
void
CCoutRedirector::ClearOutput()
{
m_stream.str("");
}
</pre><br />
It’s pretty self explanatory. StartRedirecting saves off the old cout and cerr’s streambufs, and replaces them with that of our own ostringstream. GetOutput returns the string from that stream; ClearOutput clears it, and finally, StopRedirecting restores the previous streambufs. Easy as pi.<br />
<br />
With the test I wrote above this works flawlessly. There’s only one major issue, as I later discovered: while this works for cout and cerr, it does NOT work for stdout and stderr in general. Meaning, calls to printf will not be redirected. I’d not realized that stdout and cout weren’t synonymous. For me this was a deal-breaker, but perhaps others will find it useful. <br />
<br />
In the next post I’ll show the technique I ended up using, that works for stdout/stderr as well as cout/cerr.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-5167914722208006382011-06-01T20:23:00.000-07:002013-04-24T23:14:31.564-07:00C Arrays: Here There Be Dragons<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/c-arrays-here-there-be-dragons/">here</a>. Please update your links.
<hr><br/>
I was just answering <a href="http://stackoverflow.com/questions/6195045/malloc-creates-space-for-single-struct-not-array-of-structs/6207941#6207941">a question on Stackoverflow</a> (funny how often that inspires me to blog something) about using a C array in a Cocoa app.<br />
<br />
The short answer is, you almost never want to do that. Sure, there are times when you might (if you’re going to end up passing it to some C function that needs a raw array; OpenGL or cocos2d, perhaps). In general, though, using some kind of container object is almost always a better solution.<br />
<br />
This issue hit home for me, not too long ago. While developing <a href="http://www.fat-apps.com/apps/puzzletiles-hd">PuzzleTiles</a>, we needed a data model to store game state. Hmm, a two dimensional grid of tiles? My knee-jerk reaction was to type this:<br />
<pre class="brush: c">const int MAX_ROWS = 8;
const int MAX_COLUMNS = 8;
@interface GameState : NSObject
{
// stuff
tileGrid[MAX_ROWS][MAX_COLUMNS];
// more stuff
}
@end
</pre><br />
Almost as soon as I typed this, I got this terrible sense of dread. As if a million memory buffers cried out, then were suddenly silenced, due to overflow.<br />
<br />
But then I came to my senses. I got my big boy pants on, see? I’m an old hand, a seasoned vet, I know damn well what I’m doing. Right? Right. <br />
<br />
I made sure to be very careful. tileGrid had no outside accessors; its state was returned via public member functions like - (int) getTileAtX:(int)x y:(int)y Only GameState could muck with the grid, internally, and I was very careful about that. All accessors did sanity checks on all input and output parameters. I wrote code that, in Debug builds, would do an internal consistency check on tileGrid after each tile move operation, to make sure nothing went bad. I knew I had this stuff figured out.<br />
<br />
Weeks passed. All memories of tileGrid faded away; we had other issues we were dealing with as we neared our release. GameState was rarely even looked at; it was a pretty simple piece of code, and was (as far as we knew) rock solid. And yet... we were plagued with very rare, seemingly random crashes. Sometimes we’d see them once or twice in a day, sometimes not for several days in a row. If we ever did get to see one in the debugger, or view a crash log, it was complete nonsense. Code that should never crash, was. And it was rarely the same piece of code that failed. It was maddening. For weeks we fought this, scrutinizing every memory allocation and release; running over and over again in Leaks; poring over all our memory management code for any hint of trouble. <br />
<br />
In desperation, I eventually returned to GameState. I added some extra consistency checking, and... saw it fail within a minute of running the game. It turned out my original consistency checking code was missing one corner case, and it turned out that case happened pretty often. When it did fail, one part of GameState’s internal code would fail to find a tile, and return the invalid value {-1, -1}. Then another part of the code would not bother to check for the invalid value, and would happily use it to index into tileGrid, accessing tileGrid[-1][-1], effectively randomly overwriting 4 bytes of whatever poor object happened to be 36 bytes before it. Now the random maddening crashes were completely clear.<br />
<br />
It wasn’t all bad. While this bug eluded us, we spent a whole lot of time seriously scrutinizing our memory management. In the end, by the time we finally nailed this bug, the entire app was completely bullet proof.<br />
<br />
Lesson (re-) learned: if you find yourself using a raw C array, think really hard. Is that really the best way to go? We were careful, and knew what we were doing, and still paid a price. Here there be dragons.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-20367991403414438602011-05-19T22:32:00.000-07:002013-04-24T23:14:42.112-07:00Stuff I Can't Live Without Part I: conditional NSLogging<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/stuff-i-cant-live-without-part-i-conditional-nslogging/">here</a>. Please update your links.
<hr><br/>
I spend a great deal of time watching my code. By that I mean either stepping through it in the debugger, or reading logs generated by it to see what it’s done. You might think you know exactly what your software is doing, but the truth is, if you don’t watch it run, you don’t know.<br />
<br />
During the course of development, I usually end up adding a fair amount of logging code, and of course I don’t want that in my production builds. So a while back I went a-Googling for some conditional NSLog calls, and found <a href="http://iphoneincubator.com/blog/debugging/the-evolution-of-a-replacement-for-nslog">these great macros</a>, courtesy of <a href="http://iphoneincubator.com/blog/">Nick Dalton’s iPhoneIncubator</a>. I use them (and repeat them below) nearly verbatim. This is the first thing I add to any project I’m working on. I’m naked without my DebugLog (and not in the good way). <br />
<br />
<pre class="brush: c">// DebugLog is almost a drop-in replacement for NSLog
// DebugLog();
// DebugLog(@“here”);
// DebugLog(@“value: %d”, x);
// Unfortunately this doesn’t work: DebugLog(aStringVariable); you have to do this instead: DebugLog(@“%@“, aStringVariable);
// Shamelessly stolen (and edited) from http://iphoneincubator.com/blog/debugging/the-evolution-of-a-replacement-for-nslog
#ifdef DEBUG
#define DebugLog(fmt, ...) NSLog((@“%s:%d “ fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#define DebugLogFunc() NSLog( @“%s:%d”, __PRETTY_FUNCTION__, __LINE__ );
#else
#define DebugLog(...)
#define DebugLogFunc()
#endif
// AlwaysLog always displays output regardless of the DEBUG setting
#define AlwaysLog(fmt, ...) NSLog((@“%s:%d “ fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
</pre><br />
Of course this assumes you have DEBUG #defined in your debug builds somewhere (in Xcode 4 do this in Preprocessor Macros in Build Settings).<br />
<br />
The only changes I made over Nick’s is renaming them (I prefer clarity over brevity), minor formatting, and adding the ...Func macros for the common (for me) case of simply outputting the function name with no further description. This is useful for making sure the functions you expect are getting called, really are.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com1tag:blogger.com,1999:blog-2576103430982760618.post-30083228573258550772011-04-13T20:15:00.000-07:002013-04-24T23:14:49.199-07:00PuzzleTiles HD 1.0 is out!<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/puzzletiles-hd-1-0-is-out/">here</a>. Please update your links.
<hr><br/>
Shameless plug: we just released <a href="http://www.fat-apps.com/puzzletiles-ipad">PuzzleTiles HD</a> 1.0, on the app store. <a href="http://www.fat-apps.com/puzzletiles-ipad">Check it out here</a>, or just <a href="http://itunes.apple.com/us/app/puzzletiles-hd/id425981552?mt=8">go on and buy it</a>.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com1tag:blogger.com,1999:blog-2576103430982760618.post-65467834155824610702011-03-09T19:37:00.000-08:002013-04-24T23:15:11.895-07:00Loading xib files in iPad/iPhone Universal app<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/loading-xib-files-in-ipadiphone-universal-app/">here</a>. Please update your links.
<hr><br/>
I'm making an iPad enhanced version of <a href="http://www.fat-apps.com/apps/puzzletiles.html">PuzzleTiles</a>, and have thus been doing a lot of reading on doing Universal apps.<br />
<br />
One thing just about every Universal app has to do is to have separate xib files, because the iPhone and iPad have completely different form factors. I've stumbled across this idiom continuously in the past few days:<br />
<br />
<pre class="brush:c">if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
mainMenu = [[MainMenu alloc] initWithNibName:
@"MainMenu-iPad" bundle:nil];
}else{
mainMenu = [[MainMenu alloc] initWithNibName:
@"MainMenu" bundle:nil];
}
</pre>
<br />
Well, that's all well and good, but it turns out that initWithNibNamed:bundle: supports device modifiers. So instead of the above, you can use this:<br />
<br />
<pre class="brush:c">mainMenu = [[MainMenu alloc] initWithNibName:
@"MainMenu" bundle:nil];
</pre>
<br />
The magic is in the file naming. Name your files "MainMenu.xib" for the iPhone version and "MainMenu~ipad.xib" for the iPad version. iOS will automatically load the device-specific version when running on that device. Much easier, no?<br />
<br />
I found no official Apple documentation which explicitly states that the device modifier mechanism works for xib files, but it definitely works, back to iOS 4.0, near as I can tell. Relevant StackOverflow question <a href="http://stackoverflow.com/questions/5191472/ios-using-device-modifiers-for-loading-xib-files/">here</a>. I'm always on the lookout for ways to remove code, so I'll take it.
<br />
<br />
<b>Update:</b> As noted below in the comments, device modifiers are case sensitive, so make sure you're doing "MyView~ipad.xib", not "MyView~iPad.xib", or else the magic won't work. Relevant Apple documentation <a href="https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/LoadingResources/Introduction/Introduction.html#//apple_ref/doc/uid/10000051i-CH1-SW2">here</a>.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com6tag:blogger.com,1999:blog-2576103430982760618.post-75884015097965326232011-02-22T09:17:00.000-08:002013-04-24T23:15:19.196-07:00UIAlertView with blocks, revisited<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/uialertview-with-blocks-revisited/">here</a>. Please update your links.
<hr><br/>
One issue with my UIAlertView with blocks class, <a href="http://zpasternack.blogspot.com/2010/10/uialertview-with-blocks.html">here</a>, is that it only supports willDismiss, not any other delegate methods supported by UIAlertViewDelegate. At the time, that was all I needed, but more recently, my requirements changed. (Hint: if you’re presenting a UIActionSheet from within a willDismiss delegate method call, bad things will probably happen, as of iOS 4.2). <br />
<br />
So I've updated it to support all the other UIAlertViewDelegate methods. It’s pretty straightforward.<br />
<br />
First we make typedefs for each type of block, one corresponding to each UIAlertViewDelegate method.<br />
<br />
<pre class="brush: c">typedef void (^WillPresentBlock)(void);
typedef void (^DidPresentBlock)(void);
typedef void (^DidCancelBlock)(void);
typedef void (^ClickedButtonBlock)(NSInteger);
typedef void (^WillDismissBlock)(NSInteger);
typedef void (^DidDismissBlock)(NSInteger);
</pre><br />
Then we need an instance variable, and a property for each.<br />
<br />
<pre class="brush: c">@interface ZPAlertView : UIAlertView <uialertviewdelegate> {
WillPresentBlock willPresentBlock;
DidPresentBlock didPresentBlock;
DidCancelBlock didCancelBlock;
ClickedButtonBlock clickedButtonBlock;
WillDismissBlock willDismissBlock;
DidDismissBlock didDismissBlock;
}
@property (nonatomic, copy) WillPresentBlock willPresentBlock;
@property (nonatomic, copy) DidPresentBlock didPresentBlock;
@property (nonatomic, copy) DidCancelBlock didCancelBlock;
@property (nonatomic, copy) ClickedButtonBlock clickedButtonBlock;
@property (nonatomic, copy) WillDismissBlock willDismissBlock;
@property (nonatomic, copy) DidDismissBlock didDismissBlock;
@end
</pre><br />
Note that the properties are set for copy. Retaining a block doesn’t work as you’d expect; we need to make a copy. <br />
<br />
We synthesize our properties. No need for custom stuff here.<br />
<br />
<pre class="brush: c">@synthesize willPresentBlock;
@synthesize didPresentBlock;
@synthesize didCancelBlock;
@synthesize clickedButtonBlock;
@synthesize willDismissBlock;
@synthesize didDismissBlock;
</pre><br />
Since we’ve made our properties copy, we need to make sure we release them in dealloc. <br />
<br />
<pre class="brush: c">- (void) dealloc
{
[willPresentBlock release];
[didPresentBlock release];
[didCancelBlock release];
[clickedButtonBlock release];
[willDismissBlock release];
[didDismissBlock release];
[super dealloc];
}
</pre><br />
The real magic is in our overridden show method, which doesn’t look very much like magic at all:<br />
<br />
<pre class="brush: c">- (void) show
{
self.delegate = self;
[super show];
}
</pre><br />
Finally we have the actual delegate method calls, each of which call the appropriate block. That’s it.<br />
<br />
<pre class="brush: c">- (void) willPresentAlertView:(UIAlertView *)alertView
{
if( willPresentBlock != nil ) {
willPresentBlock();
}
}
- (void) didPresentAlertView:(UIAlertView *)alertView
{
if( didPresentBlock != nil ) {
didPresentBlock();
}
}
- (void) alertViewCancel:(UIAlertView *)alertView
{
if( didCancelBlock != nil ) {
didCancelBlock();
}
}
- (void) alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if( clickedButtonBlock != nil ) {
clickedButtonBlock(buttonIndex);
}
}
- (void) alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
if( willDismissBlock != nil ) {
willDismissBlock(buttonIndex);
}
}
- (void) alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if( didDismissBlock != nil ) {
didDismissBlock(buttonIndex);
}
}
</pre><br />
The client code would look like this:<br />
<br />
<pre class="brush: c">- (void) doLengthyOperationPrompt
{
enum {
ButtonIndexCancel = 0,
ButtonIndexDoItNow,
ButtonIndexDoItLater
};
ZPAlertView *anAlert = [[ZPAlertView alloc] initWithTitle:@"Warning!"
message:@"Would you like to perform a really lengthy operation?"
delegate:nil
cancelButtonTitle:@"Nope"
otherButtonTitles:@"Yeah, sure", @"Meh, maybe later", nil];
anAlert.didDismissBlock = ^(NSInteger buttonIndex){
switch( buttonIndex ) {
case ButtonIndexDoItNow:
[self performLengthyOperation];
break;
case ButtonIndexDoItLater:
[self scheduleLengthyOperationForLater];
break;
}
};
[anAlert show];
[anAlert release];
}
</pre><br />
I'll try to put the full source and and example project up somewhere, soon. Any and all feedback is welcome.<br />
<br />
<b>Update</b>: It's now up on github, <a href="https://github.com/zpasternack/ZPAlertViewTest">here</a>.zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-49044877018777817702011-02-14T22:57:00.000-08:002013-04-24T23:15:26.172-07:00Why Didn't It Come With This? Part 1<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/why-didnt-it-come-with-this-part-1/">here</a>. Please update your links.
<hr><br/>
Sometimes I find myself writing the same function over and over, and I wonder, "Why didn't it come with this?" Here's one I did today. I have a UIImage which I want to scale to a different size. Actually, this happens in 3 or 4 places in <a href="http://www.fat-apps.com/apps/puzzletiles.html">PuzzleTiles</a>. I finally broke down and made it a category on UIImage. Hopefully you find it useful as well.<br />
<br />
<pre class="brush: c">// UIImage+Resizing.h
@interface UIImage (Resizing)
- (UIImage*) scaledImageWithSize:(CGSize)newSize;
@end
// UIImage+Resizing.m
#import "UIImage+Resizing.h"
@implementation UIImage (Resizing)
- (UIImage*) scaledImageWithSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions( newSize, NO, 0.0 );
CGRect scaledImageRect = CGRectMake( 0.0, 0.0, newSize.width, newSize.height );
[self drawInRect:scaledImageRect];
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
@end
</pre>zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com0tag:blogger.com,1999:blog-2576103430982760618.post-79941741290794755392010-10-19T14:27:00.000-07:002013-04-24T23:15:33.133-07:00UIAlertView with blocks<em>Note:</em> This blog is deprecated. <a href="http://www.zpasternack.org"</a>@synthesize zach</a> has moved to a new home, at <a href="http://zpasternack.org">zpasternack.org</a>. This blog entry can be found on the new blog <a href="http://zpasternack.org/uialertview-with-blocks/">here</a>. Please update your links.
<hr><br/>
When extending behavior in Objective-C (unlike in other OO languages), subclassing is not always your first choice. In particular, the Cocoa framework makes extensive use of the Delegation pattern. While this works very well in general, it can at times become cumbersome. Consider a ViewController that wants to display an alert:<br />
<br />
<pre class="brush: c">// MyViewController.m
#import “MyViewController.h”
enum {
ButtonIndexCancel = 0,
ButtonIndexDoItNow,
ButtonIndexDoItLater,
LengthyOperationAlertTag = 100
};
@implementation MyViewController
- (void) doLengthyOperationPrompt {
UIAlertView *anAlert = [[UIAlertView alloc] initWithTitle:@"Warning!"
message:@"Would you like to perform a lengthy operation?"
delegate:self
cancelButtonTitle:@"Nope"
otherButtonTitles:@"Yeah, sure", @"Meh, maybe later", nil];
anAlert.tag = LengthyOperationAlertTag;
[anAlert show];
[anAlert release];
}
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
if( alertView.tag == LengthyOperationTag ) {
switch( buttonIndex ) {
case ButtonIndexDoItNow:
[self performLengthyOperation];
break;
case ButtonIndexDoItLater:
[self scheduleLengthyOperationForLater];
break;
}
}
}
@end
</pre><br />
But Zach, I hear you say, what’s wrong with that? And I agree, it’s not too terrible taken on it’s own. Thing is, I’m betting MyViewController does a whole lot of things other than showing an alert. Meaning, the chances are good that your doLenghyOperationPrompt and alertView:willDismissWithButtonIndex: methods are actually not right next to each other. And, they’re probably not at the top either. So you have three related pieces of code (the enums, the code to show the alert, and the code to handle the button press) which are most likely physically separated from one another. Now imagine MyViewController has a half-dozen different UIAlertViews, each with different buttons and tags and code called in response. It adds up to a whole lotta ugly pretty quickly. <br />
<br />
Wouldn’t it be better if all three related pieces of code were in the same place? In my dream, it would look like this:<br />
<br />
<pre class="brush: c">// MyViewController.m
#import “MyViewController.h”
@implementation MyViewController
- (void) doLengthyOperationPrompt {
enum {
ButtonIndexCancel = 0,
ButtonIndexDoItNow,
ButtonIndexDoItLater
};
UIAlertView *anAlert = [[UIAlertView alloc] initWithTitle:@"Warning!"
message:@"Would you like to perform a lengthy operation?"
delegate:self
cancelButtonTitle:@"Nope"
otherButtonTitles:@"Yeah, sure", @"Meh, maybe later", nil];
[anAlert showWithCompletion:^(NSInteger buttonIndex) {
switch( buttonIndex ) {
case ButtonIndexDoItNow:
[self performLengthyOperation];
break;
case ButtonIndexDoItLater:
[self scheduleLengthyOperationForLater];
break;
}
}];
[anAlert release];
}
@end
</pre><br />
The code that handles the button presses is right there next to the code that displays the alert, preventing you from having to scroll around to follow the flow. In addition, because they’re in the same scope, we don’t have to declare the enum at the top - this is the only function that uses it - so it’s also there in the same place. Nice and compact, and all the related functionality is in close physical proximity. But how do we get there?<br />
<br />
As I mentioned before, subclassing isn’t always the first choice as a means to extend functionality. My knee-jerk inclination was to implement this as a class extension to UIAlertView. Unfortunately, extensions can only add member functions, not iVars, making this approach problematic. The easy way, in this case, is to subclass.<br />
<br />
It seems simple enough, we’ll need a showWithCompletion: member function into which we pass our block. Our class will need an iVar to hold the block. Then showWithCompletion: can set delegate to itself, and call the block on it’s own alertView:willDismissWithButtonIndex:. <br />
<br />
It looks a little somethin’ like this:<br />
<br />
<pre class="brush: c">// ZPAlertView.h
#import <UIKit/UIKit.h>
typedef void (^AlertCompletion)(NSInteger);
@interface ZPAlertView : UIAlertView <uialertviewdelegate> {
AlertCompletion completionBlock;
}
- (void) showWithCompletion:(AlertCompletion)aBlock;
@end
// ZPAlertView.m
#import "ZPAlertView.h"
@implementation ZPAlertView
- (void) showWithCompletion:(AlertCompletion)aBlock {
self.delegate = self;
completionBlock = [aBlock copy];
[self show];
}
- (void) alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
completionBlock(buttonIndex);
[completionBlock release]; completionBlock = nil;
}
@end
</pre><br />
Stupid, simple right?<br />
<br />
We start out by creating a typedef for our block. It takes a single parameter of type NSInteger and has no return value. This isn’t quite the same as alertView:willDismissWithButtonIndex, because we don’t supply the alertView. There’s no need to, because it’s right there in the scope of our block. We add one function, showWithCompletion:, which takes the block which will be executed. <br />
<br />
showWithCompletion: sets the delegate to self (because we’ll be handling alertView:willDismissWithButtonIndex: ourselves), stores off a copy of the completion block, and calls [self show]. We take a copy of the block, because the block was created on the stack, and will be going out of scope before we execute it later, so the copy gives a copy on the heap which will stick around until we’re done with it.<br />
<br />
The magic happens in alertView:willDismissWithButtonIndex:. We simply call the block, and then release it. That’s it.<br />
<br />
Using this class, we can do exactly like I wanted above, by only replacing UIAlertView with ZPAlertView:<br />
<br />
<pre class="brush: c">// MyViewController.m
#import “ZPAlertView.h”
#import “MyViewController.h”
@implementation MyViewController
- (void) doLengthyOperationPrompt {
enum {
ButtonIndexCancel = 0,
ButtonIndexDoItNow,
ButtonIndexDoItLater
};
ZPAlertView *anAlert = [[ZPAlertView alloc] initWithTitle:@"Warning!"
message:@"Would you like to perform a lengthy operation?"
delegate:self
cancelButtonTitle:@"Nope"
otherButtonTitles:@"Yeah, sure", @"Meh, maybe later", nil];
[anAlert showWithCompletion:^(NSInteger buttonIndex) {
switch( buttonIndex ) {
case ButtonIndexDoItNow:
[self performLengthyOperation];
break;
case ButtonIndexDoItLater:
[self scheduleLengthyOperationForLater];
break;
}
}];
[anAlert release];
}
@end
</pre><br />
<br />
You can do lots of cool stuff with this. Want to put a text field on there? No problem!<br />
<br />
<pre class="brush: c">- (void) doAlertWithTextField {
ZPAlertView *alert = [[ZPAlertView alloc] initWithTitle:@"Hello!"
message:@"Please enter your name:\n\n\n"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"OK", nil];
UITextField *nameEntryField = [[UITextField alloc] initWithFrame:CGRectMake(12, 90, 260, 25)];
nameEntryField.backgroundColor = [UIColor whiteColor];
nameEntryField.keyboardType = UIKeyboardTypeAlphabet;
nameEntryField.keyboardAppearance = UIKeyboardAppearanceAlert;
nameEntryField.autocorrectionType = UITextAutocorrectionTypeNo;
nameEntryField.clearButtonMode = UITextFieldViewModeWhileEditing;
[alert addSubview:nameEntryField];
[nameEntryField becomeFirstResponder];
[nameEntryField release];
[alert showWithCompletion:^(NSInteger buttonIndex) {
UIAlertView *anAlert = [[UIAlertView alloc] initWithTitle:@"Greetings!"
message:[NSString stringWithFormat:@"Hello, %@", nameEntryField.text]
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"OK", nil];
[anAlert show];
[anAlert release];
}];
[alert release];
}
</pre><br />
Normally when you do such a thing (<a href="http://zpasternack.blogspot.com/2010/09/uialertview-with-uitextfield.html">as I’ve written about before</a>), you need to set a tag on the UITextField so you can find it later in alertView:willDismissWithButtonIndex to get at the text. This way, there’s no need for that, because it’s still in the scope of our block.<br />
<br />
One limitation of this is that UIAlertViewDelegate has a bunch of delegate methods aside from alertView:willDismissWithButtonIndex:. If you wanted to do something in other delegate methods, you’d need to modify it to do that. For my purposes, all I needed was willDismissWithButtonIndex. Also, showWithCompletion: is replacing whatever delegate you specified in your initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles: call, so you can’t really use any of the other delegate methods (of course, you could just call show if you wanted to do that, or just use a regular UIAlertView).<br />
<br />
One could probably implement this as a class extension, but there are issues with doing so. For one thing, you can’t add any iVars to a class exetension, only member functions. You could get around this by, say, storing a static dictionary mapping UIAlertViews to AlertCompletion blocks. But that is kinda icky. Plus, the semantics of an NSDictionary are to copy the key, so you couldn’t use the UIAlertView as the key, unless you take the integer value of the pointer and wrap that in a NSNumber. Double icky. That’s about as far along as I’ve gotten in the process of making this into a class extension, but if someone has a better idea, I’d love to hear it.<br />
<br />
Note that I haven’t used this in any production code yet -- PuzzleTiles 1.1 is close enough to release that I’m disinclined to make such seemingly gratuitous changes -- so I don’t know if there are any gotchas I haven’t thought of. Use it at your own risk, I’m saying. If you do find use for it, or have ideas for how to improve it, let me know!zpasternackhttp://www.blogger.com/profile/04594195725149809667noreply@blogger.com3