SwipeHead
Most of us, at some point, will apply a label of sorts to ourselves. Are we teachers, rocket surgeons, hippies, nihilists, or maybe middle-aged? A single label rarely suffices, and I’ve gone through a few myself. I’ve alleged myself to be a musician, a coder, a technician, a writer (hah), and had a few other stints. There’s a common thread here: I like making things. With that out of the way, in the middle of December I helped to make this:
This is SwipeHead, a sort of hybrid puzzle game for the iPhone and iPod touch. I’d explain how it works, but figuring that out is one of the more satisfying puzzles. It reminds me a bit of old LucasArts adventure games in which a challenge would be posed and various tools offered, but after that it was up to the player and their wits. SwipeHead, likewise, is meant to have an intuitive learning curve and once it gets going, is very enjoyable.
Though I might seem to be trumpeting my own horn, the real kudos go to the game designer, Leanne, over at Wizkeit Games, who designed and produced the whole thing. For my own part, it’s humbling to see my code executing on other devices and available on the iTunes Store. I know there iTunes Store is a pretty saturated market these days, but it doesn’t detract one bit from the glee. More horn trumpeting yet: it was accepted on the first submission, which Leanne and I were told doesn’t happen often.
If you’re looking for a unique experience among the many app clones on the iTunes Store, give SwipeHead a try. When I first saw the design documents, it wasn’t one bit like any other puzzle game I saw out there, and remains so even post-release. Though only a mere $0.99, there’s a free version as well to whet your appetite.
[UIImage imageNamed:]
UIImage *myImage = [UIImage imageNamed:@"pony.png"];
This is probably one of the most convenient methods I’ve ever come across. Feed it a name and it will return an image. Better yet, it handles caching for you automatically so that you don’t have to. Notice something about it though? There’s no alloc, meaning you shouldn’t call release. If you can’t call release, how do you get your memory back? This isn’t a big deal when using Mac OS X’s older brother, NSImage, since your average computer these days ships with several gigabytes of memory as opposed to the paltry 25MB or so that most iPhone apps are limited to. If you’re dealing with a lot of images on the iPhone though, this hands-off caching approach is going to cause problems.
The first obvious solution is to just avoid the convenience method and manually alloc and load each image with its full path:
UIImage *myImg = [[UIImage alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/pony.png", [[NSBundle mainBundle] bundlePath]]];
That’s not awful for loading an image or two, but if you’re going to load many images, why not encapsulate this in something more elegant? Since such a method would be useful application-wide, it furthermore makes sense for the method to exist in its own application-wide class, rather than just stuffing the method willy-nilly into whatever class you’re working in. Actually, if you’re going to go to the trouble of creating a new class just for a convenience method, why not make it do other neat things? Let’s do that.
Phantom Functions
Not that they’re very spooky, but function calls that don’t do anything can be a real time-sink. The call in question: glPolygonOffset(), a terribly useful function which I was using to prevent Z-fighting. Everything worked as expected in the iPhone simulator, but nothing happened when run from the real hardware! After an hour of searching through the code and documentation, it turns out the answer is pretty straightforward: polygon offset is not supported by the hardware.
The fact that the compiler didn’t kick out a warning or error would seem to suggest that the feature is available in later models. My test platform uses PowerVR’s MBX Lite 3D GPU, as do all models up until Monday’s announcement. Still, Apple is able to tailor the SDK’s header files exactly to their devices capabilities, so it’s a touch frustrating. On the bright side, there’s a workaround for coders who needed glPolygonOffset to draw edge geometry:
#define EDGE_OFFSET 0.00001
glDepthRange(EDGE_OFFSET, 1.0);
/* draw all non-edge geometry */
glDepthRange(0.0, 1.0 - EDGE_OFFSET);
/* draw all edges */
There’s a couple more good ideas actually, but the above code works fine for a low-power device.
Instruments: Building a Watertight App
Probably one of the main criticisms I’ve hear about iPhone applications is that they have a tendency to crash. This is not isolated just to bad applications, not that it helps. If I had to make a wild guess, I’d say the main cause of such crashes is memory leakage combined with the iPhone’s apparent 128MB of memory. Sure, on a desktop with several gigabytes kicking around, you could almost afford a few sloppy leaks (not that you should), but the iPhone offers no such padding. You don’t need a big leak either as long as it keeps growing; add to this that many non-Mac developers have no previous exposure to Cocoa’s alloc-retain-release methods and you’ve got an application ready to tank.
Instruments, part of the developer tools, has a crazily useful module called Leaks that does exactly what it advertises. It keeps track of all memory allocations and deallocations and lets you know where the outstanding balance is and which method it lives in. Although early optimization is considered be on par with supervillainy, leak detection is not optimization per se and I would recommend profiling for it early and regularly. Method calls will only get deeper and more tangled as time goes on. I was surprised to learn that my own code had nine leaks, nine. That’s nine more than I was hoping for. I’ve cut it down to a single 3.5KB leak thanks to Instruments, and while that may not seem like much, I’m going to find and nuke that 3.5KB before the night is over.
Mirror, flip, inverse, invert, stop drop and roll
Not having much to compare it to, I can’t honestly say how well the developer documentation stacks up against those available on other platforms. It certainly serves me well enough, and is worlds apart from what came with MPW. The day that MPW finally started working was a small miracle; this was before Google had an answer to just about everything (great instructional video too), and most libraries assumed you were using CodeWarrior anyway. At any rate, the reason for today’s post: for everything not spelled out by the developer docs, there’s Google.
After spending a good hour trying to trackdown a [UIImage reverse] method (there is none), I’ve boiled it down to a working block of code. Best of all, it’s short! So here’s to you, Googlers, I’m putting it on the internet in hopes that another dev finds it useful. The following snippet will horizontally reverse/flip/mirror a UIImageView:
UIImageView *myView = [[UIImageView alloc] initWithImage:myImage]; myView.transform = CGAffineTransformIdentity; myView.transform = CGAffineTransformMakeScale(-1.0, 1.0);
That’s it. To flip it back:
myView.transform = CGAffineTransformIdentity;
Not so scary anymore! So what’s going on here? Get your answers from a more authoritative source. It’s a good read about a powerful property, and highly recommended for anyone who doesn’t want to create two or ten different versions of the same image at different rotations.
Breakin’ Stuff
It’s 6am and sleep is proving impossible. My brain won’t shut down, instead thinking about the enormous possibilities made available by the iPhone SDK. Objective-C is back with a vengeance; as is becoming the norm, the main() function is meant to be off-limits, and there isn’t really a substitute for it either. Instead of being a hindrance, it’s a godsend. Draw up an interface, plug in some code, connect the outlets, and bampf. Classes start talking to one another. Instead of growing an increasingly crazy tree of functions pushing values up and down, there’s actually some kind of programmatic dialogue going on. While this probably isn’t a new concept to an experienced programmer, it’s the first times that I’ve found it intuitive.
Anyway, that’s just it. The whole thing has me pretty excited and the brain won’t pipe down, instead busying itself by dreaming up ways to keep classes nicely self-contained and autonomous while actively participating in the overall program. Or, maybe a way to make views handle themselves and report when something important happens, instead of controllers polling them constantly when resources could be better spent.
Not that it’s all unicorns and rainbows. Registering, configuring, and provisioning a test device was like finding out the rainbow is made of sewage, and the unicorn just ate your last pie. You eventually work through it, but probably won’t look forward to doing it a second time.
