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

11 comments:

Daniel said...

I am hoping that apple implement the new adjustable scrubber (as seen in the ipod app in the 3.0 betas) as an option for all sliders where you need fine control.

mpatric said...

@Daniel - let's hope so, that would be great!

iphoneprogramcisi said...

Hi Michael,

What do you use to test drive Objective C code?

Cheers
Cenk

mpatric said...

@cenk - I use OCUnit. Although it can be a bit painful to setup with iPhone code, see this post.

Unknown said...

Thanks ! I was in the same trouble with UISlider inside a UITableView cell... the defaults make the slider way too much difficult to manipulate. Now, it works like a charm!

Unknown said...

Thank you so much! I didn't figure out this problem for a while. Your post helps me a lot.

Anonymous said...

Great post !
Thanks a lot for your code, I am using your code in order to have a bigger slider (35 pixel height), I added the method trackRectForBounds with the desired size, but the tracking rect in always the same.

Methods pointInside & beginTrackingWithTouch are not called outside the size of the UISlider...

Do you know what I need to do ?

Thanks for your help.

Thierry

Anonymous said...

I fixed my problem, a UIView was hidding a portion of my slider...

Thanks a lot for your post, my slider works great !

kannan said...

Thanks for the code. Still apple didn't take measures for this issue. And I'm one of victim of slider problem.

Seify said...

Great! Thank you!

Matthew said...

Thanks! I have been perplexed by the UISlider size limit for a while!