Sunday, December 05, 2010

The Trumbull Continuum

Walking down by the river, I would keep catching out of the corner of my eye glimpses of a future version of the city. (I trudge, as always, in Gibson's fleet footsteps.) These baby arcologies begin to grow, slowly encroaching on the neo-Ballardian landscape of plazas and balconies and flower-less knives of green. I've seen this elsewhere, in a beam of green light slicing the Greenwich night. I attempt to capture their image with the device I carry in my pocket, the one which is more powerful that the first seven computers I owned or the dozens which put men on the moon; the device which will let me instantaneously share with the world what I'm having for lunch or let me pull down videos of amusing cats no matter where I am. I live in the future.

Saturday, December 04, 2010

The Other 80%

Since the weekends are when I take a break from my day job coding iPhone apps, I spent this afternoon watching videos of people talking about how to code iPhone apps. If you haven't already checked out iDeveloper TV (previously the Mac Developer Network), you should.

I was particularly taken by the Pimp My App talk given by Mike Lee (who, I have to concede, probably is the World's Toughest Programmer and could easily have wee Matt Gemmell in a fight). One day I'll have to get him to visit our office, where he can wreak bloody vengeance, since most of the apps we churn out feature his bĂȘte noir, the splash screen. Most also have a little animated logo, too. With any luck he'll flatten the entire building.

But the real takeaway was the idea that polishing your app is "the other 80%": that those finishing touches should take as much time and effort as the rest of development up to that point. I completely agree with this, which makes the realities of the kind of client-driven development I do so much harder to bear. We're lucky if the first 80% of a project doesn't expand to take up 100%+ of the time allotted to it. Finishing an app is usually an almighty rush involving tracking down bugs, adding last minute feature requests, and then tracking down the bugs you just added with the last minute features. I've yet to complete a project which I can honestly say I'm fully happy with. And since I work with so many talented, creative people, I can't help feeling that this is a crying shame, and that ultimately I'm letting the side down with these implementation failures. It really shouldn't be this way.

(And on a related note, the Gordon Ramsay Cook With Me HD iPad app, my last big project, went live yesterday, complete with an inexplicable bug which silenced the sound effects. Again I manage to snatch failure from the jaws of success. This is doubly bad, since I'm on record as not being a fan of them. I didn't do it deliberately, honest.)

Thursday, November 04, 2010

Thinking Aloud About the Mac App Store

With the news that Apple has started accepting submissions for the Mac App Store — and with the announced launch date just a couple of months away — I'm sure I'm not alone in being an indie developer giving serious consideration to whether the store is the right place for me to sell my applications.


On the surface, this decision should be a no-brainer. The App Store model has succeeded beyond anyone's expectations on iOS, its high-profile nature driving hundreds of thousands of users who would not have otherwise to make purchases. And in case you're in any doubt as to the power of an Apple-managed store front, I present the following:



Can you spot the point where SimCap entered Apple's OS X Downloads list? No, the absolute numbers aren't very impressive, but relatively I'm very happy with the results. And remember that this is a relatively niche developer / marketing tool we're talking about here.


So why would I possibly want to say no to this kind of opportunity, most likely on a much larger scale?


There have already been many posts on these subjects which enumerate the potential drawbacks in a far more depth and with far more eloquence than I can. But from all those potentials, I see the lack of a demos as the biggest stumbling block.


While I'm sure that SimCap would pass Apple's entry requirements (for instance, it doesn't use any private APIs mainly because I wouldn't know where to find these private APIs), there's still something a little hackish about it. To be perfectly honest, there are some cases where SimCap simply won't work for users, and no matter what I do I'm unable to offer a solution or recreate the problem. In these cases the only option left to me is to apologise to the user for the time wasted. With SimCap currently available as a full demo, this is a frustration (on this side as well as the customer's, believe me), but it isn't a problem in that no money has changed hands. Maybe I lack faith in my own work, but to be perfectly honest I'm reluctant to charge up-front for SimCap when I can't guarantee that it will work every time for every user.


(The chart, by the way, is from Tyler Hall's excellent Shine dashboard. Highly recommended, if you decide to stay out of the Mac App Store.)

Saturday, October 30, 2010

OS X Names Aren't Nearly As Cool As They Used To Be

Grab almost any Makefile from Apple's opensource code repository (this is from cctools) and behold:

ifeq "macos" "$(RC_OS)"
STATIC := $(shell if [ "$(RC_RELEASE)" = "Beaker" ] || \
[ "$(RC_RELEASE)" = "Bunsen" ] || \
[ "$(RC_RELEASE)" = "Gonzo" ] || \
[ "$(RC_RELEASE)" = "Kodiak" ] || \
[ "$(RC_RELEASE)" = "Cheetah" ] || \
[ "$(RC_RELEASE)" = "Puma" ] || \
[ "$(RC_RELEASE)" = "Jaguar" ] || \
[ "$(RC_RELEASE)" = "Panther" ] || \
[ "$(RC_RELEASE)" = "MuonPrime" ] || \
[ "$(RC_RELEASE)" = "MuonSeed" ] || \
[ "$(RC_RELEASE)" = "SUPanWheat" ]; then \
echo "-static" ; \
else if [ "$(RC_RELEASE)" = "Tiger" ]; then \
echo "-static" \
else \
echo "" \
; fi; fi; )
else
STATIC = -static
endif

Lion? I want Mac OS Xs Cookie Monster, Grover and Elmo.

Friday, September 03, 2010

Please Stop Squee-ing Over iOS

A quick public service announcement, inspired by the current level of excitement over the fact that the new AppleTV may be running iOS. Firstly, the differences between iOS and OS X (remember OS X?) are like the differences between Ubuntu and Red Hat. Yes, iOS is the new shiny. And, yes, without iOS I probably wouldn't be able to make a living as an Objective-C programmer. But the differences are pretty superficial and only a little more than skin deep. (Which, by the way, is why I could hit the ground running, while all you ex-C# guys are still trying to get your heads around reference counting and wondering out loud what this delegate shit is about.)

Secondly, you can't wait until people jailbreak the new AppleTV and start writing iOS apps for it? Really? Cocoa Touch is a great library - superior in a number of ways to AppKit, which is starting to look a little long in the tooth - but it was written to fit a very particular use case. I'll give you a hint: the clue's in the name. The AppleTV is running on an A4 because the chip is small, cheap and runs cool. I haven't found the figures yet, but I'm going to bet it only just matches the performance of the Pentium M (also clocked at 1GHz) from the original version. Both generations of machines are running a custom UI built on top of Core Animation. And yet we only get the excitement now. I guess the magic word is 'apps', and the world and his dog seem to think they can download XCode, buy a couple of books and strike it rich. Sigh.

Sunday, August 01, 2010

Customising the Appearance of UITabBarController

(I tried out a couple of titles for this post, one of which was "Customising the Appearance of UITabBar". But I realised that, while in effect this is how you could describe the problem, it wasn't really accurate. To all intents and purposes, UITabBarController is a single opaque entity. Yes, there's a UITabBar in there somewhere, but that knowledge alone is of little use to you. I get the feeling that UITabBar has its own documentation only because otherwise UITabBarController's would be even longer than it already is.)

The Problem: Whichever finger painter you're currently beholden to has decided that your next app is going to use a tab bar. Only not Apple's tab bar. Oh no. Because that's what ever other app looks like and it just doesn't look designed and anyway they want all seventeen tab buttons to appear at once without that "More" button thing and mwaa mwaa mwaa mwaa mwaa.

So you're left with the task of reimplementing UITabBarController, something which originally took a team of Apple developers about three times as long as you've been allocated for the entire project. And you know that you won't even get close, because you'll loose all that useful Apple goodness like -viewWill(Did)(Dis)Appear messages which actually propagate to sub-view controllers and being able to honour hidesBottomBarWhenPushed from the depths of the navigation stack.

What you'd really like to do is just change how the regular UITabBar displayed by UITabBarController looks.

The Warning: I'm using this code in a production project, but I haven't submitted it to the App Store yet. There's a chance that adopting this approach may get your app rejected. (Although chances are that's more likely to happen bceause your new style tab bar looks too much like Apple's own, rather than because of anything happening at the code level.)

This has been tested on 3.1.3 and 4.0.1, but it's exactly the kind of evil hackery which is likely to break with a future iOS upgrade.

The Solution: We're going to create a UITabBarController subclass (yes, even though Apple says not to in the second line of the docs). Here's the code. And here is an example project (based on the default iPhone tab bar template) for the impatient among you).

SJCTabBarController.h:

#import <uikit/uikit.h>

@interface SJCTabBarController : UITabBarController {
UIView *_fakeTabBar;
UIButton *_currentSelection;
}

@property (nonatomic,assign) IBOutlet UIView *fakeTabBar;

-(IBAction)fakeTabTapped:(id)sender;

@end

SJCTabBarController.m:

#import "SJCTabBarController.h"

@implementation SJCTabBarController

@synthesize fakeTabBar=_fakeTabBar;

// evil trickery happens here
-(UITabBar *)tabBar { return nil; }

-(void)viewDidLoad {
[super viewDidLoad];

// install our fake tab bar
[[super tabBar] addSubview: _fakeTabBar];

// set up the default selected tab
// you may like to read the tab/tag number from the user defaults
[self fakeTabTapped: [_fakeTabBar viewWithTag: 0]];
}

// switch tabs
-(IBAction)fakeTabTapped:(id)sender {
// do we need to do anything?
if(sender == _currentSelection) return;

// un-select the currently selected button
_currentSelection.selected = NO;

// select the new button
_currentSelection = (UIButton *)sender;
_currentSelection.selected = YES;
self.selectedIndex = _currentSelection.tag;

// you may like to write the selected index into user defaults here
}

-(void)viewDidUnload {
[super viewDidUnload];

_fakeTabBar = nil;
_currentSelection = nil;
}

@end

Explanations in a second, but first the Interface Builder part of the equation.

In IB, drag in a UITabBarController (or use the one from the MainWindow.xib created with the default tab bar XCode template) and add view controllers to it. Then change it's class to our UITabBarContoller sub-class. Now add a view to act as your new-look tab bar. This should be a screen wide by the standard 49 points high. Connect it to the fakeTabBar outlet in the subclass. Give it a non-zero tag higher than the number of tabs you want to add.

Now add a button for each tab. Give them a tag which is the same as the index of the view controller you want selected when they're tapped (eg. the first is 0, the second 1, etc.). Wire them up to send -fakeTabTapped: to the sub-class.

You should end up with a hierarchy like this (I've added a total of six view controllers to the tab bar controller in order to demonstrate how awesomely it all works):



Which will produce something like this (which, by the way, is why no designer working with me needs worry about their job security):



So what's going on in this code? It's really rather simple, and yet I couldn't find anything quite like it on the Googles (which suggests that I've done something incredibly stupid and just not noticed it. I bet you have. The comments are down there). Our custom tab bar is added as a subview of the existing tab bar. We access this via [super tabBar] because we've overridden the getter for the tabBar property to return nil. We do this to stop Apple's code (which is well behaved and seems to always use the property accessors rather than going straight to the ivar) from altering what it thinks is a standard tab bar. Comment-out this method and you'll see a ghostly "More" tab and various labels appearing.

One last fix. You'll notice that when you select a tab which should have been managed by the "More" tab, a navigation bar with a back button entitles "More" will automatically appear at the top of the view. You can remove this by calling

[self.navigationController setNavigationBarHidden: YES];

in the -viewWillAppear: method of these view controllers. This has the unfortunate side effect of preventing you from using a navigation controller-managed navigation bar in these controllers, but the chances are that if your designer doesn't want to use the standard tab bar, they won't want this behaviour either.

(Edit: And immediately after posting this I realised that, in -fakeTabTapped:, instead of exiting if the same tab was selected again we should be checking for a navigation controller and popping all of its subviews. I'll leave that as an exercise for the reader.)