Wednesday, 15 April 2009

More responsive sliders on the iPhone

One of the best things about the iPhone is you can have thin fingers or fat fingers, and pressing buttons and using controls on the touch screen is still quite easy. On old-school touch screen devices (WinMob phones in particular spring to mind), one had to be very precise as the first location touched on the screen immediately triggered a touch event, which is why a stylus was almost always necessary. The iPhone takes a slightly different approach, where a touch that consists of many points on the screen (as would occur with a finger) is converted into a co-ordinate through some sort of averaging of all the points. In general this works really well. However, using this approach still requires the controls on the screen be sufficiently large that a user will be able to put a finger over it, with the average falling comfortably on the control. From my experiments, I believe a control needs to have a touch area of at least 32x32 pixels.

Unfortunately, some of the standard controls suffer from having a touch area that is too small and subsequently difficult to press. In particular, when creating an info type UIButton with the Interface Builder the dimensions and bounding rect are set to 18x19, which is too small. This can be fixed by changing the bounding rect through code, but not in the interface builder.

I recently ran into a similar issue with a slider control.


When creating a UISlider with the Interface Builder, it is given a fixed height of 23 pixels, which is too low. More troubling is the thumb control in the slider, whose size is not exposed, but is around 20x20 and is too small for the slider to be comfortably moved. I found with a horizontal slider slid all the way to the left or all the way to the right, it would be difficult to move the thumb control off the edge with my finger - and I have fingers that are relatively slender. The bounding rect of the slider and the touchable area of the thumb control are as follows:


Unfortunately, simply changing the bounding rect on the control was not a solution in this case as it simply makes the slider bigger and does not change the touchable area of the thumb control. I tried all sorts of workarounds to make the slider more responsive and eventually settled on extending the UISlider control and overriding some behaviour with the following two-pronged approach (the code follows at the bottom of the post):


  1. Override the pointInside method to make points slightly outside of the control still appear to be inside the control (without changing the size of the actual slider). I decided to extend the touchable area by 10 pixels on either side of the control and 8 pixels above and below it.

  2. Override the beginTrackingWithTouch method, which determines whether the user has clicked on the thumb control, to start tracking. This requires determining where the thumb control is based on the current value, then determining if the user's touch was close enough to it for tracking to begin. I effectively increased the thumb control size to 40x40.

The new effective bounding rect of the slider and the touchable area of the thumb control are now as follows:


To use the code below, use the Interface Builder to drop a slider on the screen and then change its class from UISlider to MySlider. It only supports horizontal sliders, the beginTrackingWithTouch method would need to be changed or extended to use it with vertical sliders.

Granted this is a hack, but until Apple sorts out this issue, I'll continue to resort to this sort of thing. I'm surprised it hasn't been sorted out already. I've seen forum posts from people having this problem with the info button, but none as yet with sliders. Am I the only one that sees this as an issue?

MySlider.h:

#import <UIKit/UIKit.h>

@interface MySlider : UISlider {
}

MySlider.m:

#import "MySlider.h"

#define THUMB_SIZE 10
#define EFFECTIVE_THUMB_SIZE 20

@implementation MySlider

- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent*)event {
CGRect bounds = self.bounds;
bounds = CGRectInset(bounds, -10, -8);
return CGRectContainsPoint(bounds, point);
}

- (BOOL) beginTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event {
CGRect bounds = self.bounds;
float thumbPercent = (self.value - self.minimumValue) / (self.maximumValue - self.minimumValue);
float thumbPos = THUMB_SIZE + (thumbPercent * (bounds.size.width - (2 * THUMB_SIZE)));
CGPoint touchPoint = [touch locationInView:self];
return (touchPoint.x >= (thumbPos - EFFECTIVE_THUMB_SIZE) && touchPoint.x <= (thumbPos + EFFECTIVE_THUMB_SIZE));
}

@end

Sunday, 16 November 2008

Merelles iPhone game released (finally)

I've been working on Merelles for the last 4 months and I'm pleased to say, I've finally finished it. It's available on the Apple App Store for a small amount.

Merelles is an abstract strategy game. It is also know as Mills, x Men's Morris (4, 6, 9 and 12 men variations) and by many other names.


Seeing as this is my technical blog (and the Apple NDA has been lifted), I feel compelled to provide some technical details. The app itself is written in Objective-C (of course) using TDD (of course!) driven by OCUnit.

The computer AI uses a negascout tree search to examine the game tree. The use of the negascout algorithm means that some pruning will occur on the game tree so that not every node needs to be examined, as long as the moves are ordered so that better moves are examined first!

Iterative deepening is applied on top of this, starting by searching one move ahead, then two, and so on. The moves that look to be good after a search are considered first when searching deeper. In addition, as the same board position can occur several times during a single tree search, moves that result in a really promising board position are classified as killer moves and examined before other moves if they occur again during the search. All this helps in obtaining an amazing prune rate of more than 90%

A question that is often asked is how do you determine if a board position is better than another board position? Unfortunately, it's a fuzzy answer. A set of heuristics is applied, which take into account things like how many pieces each player has in play, how mobile each player is (how many valid moves they have), how easily they can form a row of three pieces, and so on. The strength of the AI is determined largely by these heuristics.

There are 3 difficulty levels. For each level, the heuristics are mostly the same, with varying depths for the tree search and the maximum number of board positions that are considered. For the most complex game (the 12 men game):

  • On easy, the AI only looks 2 moves ahead.
  • On medium, the AI looks 2 to 4 moves ahead examining up to 3000 board positions.
  • On difficult, the AI looks as many moves ahead as it can in examining up to 15000 board positions - in practice this is between 4 and 8 moves ahead, depending on the state of the game.

The resulting game play feels quite natural to me, with the AI coming up with some clever strategies, which in themselves have not been programmed into it, but rather are the result of the tree search and the heuristics. I've been astounded a few times at the way it has trapped me!

The primary challenge in writing this game was to balance the heuristics to set up board positions that would yield good results later in the game rather than just constantly pursue the capture of opponent pieces, which would result in rather one dimensional game play. On top of this, it was necessary to keep the calculations light and to optimise the code as much as possible so that the tree search would not take too long, remembering that it's running on a device with limited computational power. On difficult, the deepest tree searches take up to just over 4 seconds on the iPhone.

Wednesday, 27 August 2008

Unit testing iPhone applications

The OCUnit framework is bundled with a standard xcode installation, which is handy for getting up and running with unit tests and test-driven development (TDD). Somewhat annoyingly though it does not play nicely with the Cocoa Touch framework. So, how does one go about building iPhone applications using TDD? I'm sure there are several ways of achieving this, but I wanted to stick with OCUnit. After a bit of messing about this is how I do it.

The Cocoa Touch framework encourages the use of the model-view-controller (MVC) pattern. It's only the 'V' and 'C' bits however that need to extend Cocoa Touch objects (UIView and UIViewController); you're completely free to implement the 'M' bit as you see fit. So, the basic approach I take is to write the model classes against OCUnit tests compiled against the (Cocoa) Foundation library only, and to then use these same classes in my iPhone application, which is compiled against the Cocoa Touch framework. The Foundation library exists in both the Cocoa and Cocoa Touch frameworks and is (almost) identical, although the unit tests only run against the Cocoa version.

This setup involves creating two projects: a Foundation Tool project and a Cocoa Touch project, and to reference the model code (which lives in the Foundation Tool project) directly from the Cocoa Touch project. Rather than duplicating the files that comprise the model, they are just referenced from the Cocoa Touch project (which is achieved by dragging them from the finder into the Cocoa Touch project, making sure the 'Copy items into destination group's folder' option is not checked).

The files that comprise the model objects should import Foundation/Foundation.h whereas the files in the Cocoa Touch project should generally import UIKit/UIKit.h (which is a higher-level header file that ultimately includes Foundation/Foundation.h).

What this means is that unit tests only cover the model, not the controllers or the views - so domain logic should be in the model, with the controllers simply acting as conduits for responses from this logic. In other words, there should be as little domain logic as possible in the controllers (and obviously the views, but there should not be any logic in the views anyway!)

Tuesday, 15 July 2008

App Store Trials and Tribulations

I got my first iPhone application submitted and accepted onto the App Store for its launch. Here are my impressions of the process so far.

As a developer, once you've built an application and tested it on the device, and digitally signed it with an Apple-issued distribution certificate, the application is submitted to the App Store via iTunes Connect, which is a simple web application. All the meta-data for the App Store is entered at this point. I have a few small gripes about it and was hampered by a bug that didn't let me change details on a subsequent upload, but otherwise it's simple and does the job.

The application only appears on the App Store once it has been reviewed and approved by Apple. The first time around this took two days, but the rate at which they are approving releases seems to have slowed to a crawl now. I submitted an update more than a week ago, which has yet to be approved. In fact, I now have another update ready (which should be my last). My concern around the slowness of the approval process is that if there is a pressing update you need to roll out, it will take as long as Apple decide it will take and as a developer you have no control over this.

Once the application is on the App Store, you are at the mercy of the iTunes community. I am not convinced that the reviewing mechanism used for music is appropriate to applications. I've had several low ratings (zero or one star) simply because there is a free version of something similar and mine costs £1.19. Being as objective as I can, I can honestly say that the free version is free for a reason - it's not very good. Allowing people that haven't even downloaded the application to give it a rating is opening the door to this sort of abuse. It can be really gutting to see your rating drop, especially when the reviewers haven't been fair, or even downloaded the application in the first place. This of course has the knock-on effect that fewer people download it because of the low ratings. Given the months of hard work that went into producing my application, I can't understand why someone that hasn't downloaded it is able to review it and thus directly impact how many people are likely to download it. Apple, you need to change this!

My last gripe is that Apple don't provide any download statistics to the developer. A monthly report is produced, but in the fast moving world of online sales surely daily stats wouldn't be too much to expect?

Update: old news now, but Apple are providing daily and weekly stats.