Dynamically adding UIActionSheet buttons

Every once in a while I come across a seemingly simple iPhone coding requirement, but Apple documentation just doesn’t seem to give enough pointers, and nor does web search throw up anything useful. It may just be my inability to find the right keywords to search for, but it might also be that no-one else has posted anything about it. So here is just one such thing in case it proves helpful to anyone.

The UIActionSheet is a very useful class, and I use it frequently in my apps, but its initialisation method doesn’t allow you to add buttons from an array. Instead you apparently typically just add them hardcoded as an initialiser parameter  – and almost all code examples on the web seem to use this method.

Standard example – hardcoded buttons:

- (void)testActionSheetStatic {
	// Create the sheet with buttons hardcoded in initialiser
	UIActionSheet *sheet = [[UIActionSheet alloc] 
		initWithTitle:@"Static UIActionSheet"
		delegate:self
		cancelButtonTitle:@"Cancel"
		destructiveButtonTitle:nil
		otherButtonTitles:@"Item A", @"Item B", @"Item C", nil];

	[sheet showFromRect:view.bounds inView:view animated:YES];
	[sheet release];
}

So that is all well and good if the option buttons are known in advance and never change. But what if I need to change them at runtime? It seems easy enough to add buttons dynamically instead, by leaving out those button declarations from the initialiser, and adding them afterwards instead, and the following code shows how you might assume this should be done.

Dynamically added buttons (first attempt):

- (void)testActionSheetDynamic {
	// Create the sheet with only cancel button
	UIActionSheet *sheet = [[UIActionSheet alloc] 
		initWithTitle:@"Dynamic UIActionSheet"
		delegate:self
		cancelButtonTitle:@"Cancel"
		destructiveButtonTitle:nil
		otherButtonTitles:nil];

	// Add buttons one by one (e.g. in a loop from array etc...)
	[sheet addButtonWithTitle:@"Item A"];
	[sheet addButtonWithTitle:@"Item B"];
	[sheet addButtonWithTitle:@"Item C"];

	[sheet showFromRect:view.bounds inView:view animated:YES];
	[sheet release];
}

The problem with this becomes apparent when you run it – the cancel button appears at the TOP of the sheet, whereas standard practice seems to be that it should be at the bottom. How to achieve this? I didn’t manage to find a way of doing this while adding the cancel button in the initialiser. Instead I eventually found a way of doing so by adding it also as a dynamic button.

Dynamically added buttons (also with dynamic cancel button):

- (void)testActionSheetDynamic {
	// Create the sheet without buttons
	UIActionSheet *sheet = [[UIActionSheet alloc] 
		initWithTitle:@"Dynamic UIActionSheet"
		delegate:self
		cancelButtonTitle:nil
		destructiveButtonTitle:nil
		otherButtonTitles:nil];

	// Add buttons one by one (e.g. in a loop from array etc...)
	[sheet addButtonWithTitle:@"Item A"];
	[sheet addButtonWithTitle:@"Item B"];
	[sheet addButtonWithTitle:@"Item C"];

	// Also add a cancel button
	[sheet addButtonWithTitle:@"Cancel"];
	// Set cancel button index to the one we just added so that we know which one it is in delegate call
	// NB - This also causes this button to be shown with a black background
	sheet.cancelButtonIndex = sheet.numberOfButtons-1;

	[sheet showFromRect:view.bounds inView:view animated:YES];
	[sheet release];
}

In this case the cancel button appears at the bottom, and all works as expected.

The main remaining question I had was what on earth the destructive button was (also apparently not explained in Apple documentation). Some experimentation seemed to show that it was effectively identical to a cancel button with the exception that it had RED background instead of a black one. So if you change the last example to set the destructiveButtonIndex instead of the cancelButtonIndex, then the only difference is that the button labelled “Cancel” would have a red background.

For completeness and sanity, this is the delegate code that goes with all of the above examples.

- (void)actionSheet:(UIActionSheet *)actionSheet 
		clickedButtonAtIndex:(NSInteger)buttonIndex {
	if (buttonIndex == actionSheet.cancelButtonIndex) { return; }
	switch (buttonIndex) {
		case 0:
		{
			NSLog(@"Item A Selected");
			break;
		}
		case 1:
		{
			NSLog(@"Item B Selected");
			break;
		}
		case 2:
		{
			NSLog(@"Item C Selected");
			break;
		}
	}
}

OptimalClub – A Powerful Golfing Aid

OptimalClub is the latest iPhone app that I have developed, created in association with Todd Kos of QualityGolfStats LLC, who is the app publisher. Todd has a long history in golf software, being the creator of the industry-leading OptimalFlight desktop software, used for club fitting by the pros.

Official OptimalClub App Website

OptimalClub uses the very same physics simulation model as the desktop software does, but makes it available on your iPhone in real-time, along with live local weather observations, in order to provide an accurate simulation of the actual golf shot path superimposed onto local satellite imagery of the course, according to the clubs available in your bag. This allows you to see at a glance and select which of your clubs best suits the current distance required, and also shows how far to the left or right you will need to aim in order to adjust for wind deviation, which can be substantial, even in only moderate wind conditions. Below are four screen shots showing the effect on the the shot of the wind coming from four different directions.

In this case the best club selection varies from an 8 iron (when wind is directly behind) to a 1 hybrid (when wind is head on), and the aim varies from 32 metres to the right of the target to 26 metres to the left of the target when the wind comes from either side, instead. That’s quite a range! And that is just for a 27 kmh wind (17 mph). By the way, the units used for distances and weather are of course selectable in the app settings.

The weather engine used in the app is very similar to that used in the See Breeze augmented reality wind visualisation app. However, due to the possibility of very localised wind variations or sudden weather changes in some cases, we felt that we needed to offer the user the ability to override the weather reported by the nearest weather stations, and therefore there is an option to choose between automatic and manual weather settings.

In order to customise the app to your very own set of clubs and your own unique skill and style, you can enter details of your own clubs and the typical distances you get with them. The app then uses its advanced built-in shot physics simulation to estimate the club launch parameters (speed, launch angle and ball spin) for each and every club, after which it can predict, with surprisingly good accuracy, the actual shot path and distance you will get in the field for any given set of wind and weather conditions, as well as altitude variations and changes in elevation between the tee point and the hole.

Once your clubs are set up, all you need to do is to set up the individual shot you are about to take ie. to specify your shot origin point and your target, using the side-toolpanel.

As soon as you have specified origin and target, the app shows you the nearest 5 club distance arcs – again taking into full account the current wind, weather and elevation differences.

In the above example, the wind is from the left, and due to the short distance (62 metres), the nearest shots are all partial swings using a pitching wedge (from 30 to 70% swing effort). The arcs on the map are color coded to identify which club/swing they are showing. And once you select a club (by tapping on it), you will see the full shot simulation for that club/swing, as shown below.

The hole-in-one odds are also shown for your entertainment, if you have them switched on in the app settings. Good luck!

Of course I’ve only covered some aspects of the app here. Considerable effort was put into getting the map display right (which is even rotatable via finger-gesture), and the sequence of actions required to setup a shot with as few steps and as quickly as possible, so as to minimise interference with actual game play. All-in-all this is an app that I am particularly pleased with. It has taken considerably more development effort than any of the other apps I’ve worked on, and the result is something quite unique and powerful. Of course now the challenge will be to see how much traction and visibility this app may get now that there are 300,000 other apps competing for eyeballs.

To start things off we are offering a limited time introductory sale price – 50% off ie. US$9.99, so if you are at all interested, it may be well worth your while getting it now. (NB – Sale is still on at time of writing this article.)