Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make UISlider output nice rounded numbers exponentially?

I am implementing a UISlider a user can manipulate to set a distance. I have never used the CocoaTouch UISlider, but have used other frameworks sliders, usually there is a variable for setting the "step" and other "helper" properties.

The documentation for the UISlider deals only with a max and min value, and the output is always a 6 decimal float with a linear relation to the position of the "slider nob". I guess I will have to implement the desired functionality step by step.

To the user, the min/max values range from 10 m to 999 Km, I am trying to implement this in an exponential way, that will feel natural to the user. I.e. the user experiences a feeling of control over the values, big or small. Also that the "output" has reasonable values. Values like 10m 200m 2.5km 150 km etc. instead of 1.2342356 m or 108.93837756 km.

I would like for the step size to increase by 10m for the first 200m, then maybe by 50m up to 500m, then when passing the 1000 m value, it starts to deal with Kilometers, so then it is step size = 1 km up until 50 km, then maybe 25 km steps etc.

Any way I go about this I end up doing a lot of rounding and a lot of calculations wrapped in a forrest of if statements and NSString/Number conversions, each time the user moves the slider just a little.

I was hoping someone could lend me a bit of inspiration/math help or make me aware of a more lean approach to solving this problem.

My last idea is to populate and array with a 100 string values, then have the slider int value correspond to a string, this is not very flexible, but doable.

Thank you in advance for any help given:)

like image 325
RickiG Avatar asked May 11 '10 15:05

RickiG


3 Answers

A quick scaling solution involves three special methods:

- (CGFloat)scaleValue:(CGFloat)value {
    return pow(value, 10);
}

- (CGFloat)unscaleValue:(CGFloat)value {
    return pow(value, 1.0 / 10.0);
}

- (CGFloat)roundValue:(CGFloat)value {
    if(value <=   200) return floor(value /   10) * 10;
    if(value <=   500) return floor(value /   50) * 50;
    if(value <=  1000) return floor(value /  100) * 100;
    if(value <= 50000) return floor(value / 1000) * 1000;
    return floor(value / 25000) * 25000;
}

Reacting to the slider changes would be something like:

- (void)sliderChange:(id)sender {
    CGFloat value = mySlider.value;
    value = [self scaleValue:value];
    value = [self roundValue:value];
    valueLabel.text = [NSString stringWithFormat:@"%f", value];
}

You can init with the following code:

mySlider.maximumValue = [self unscaleValue:999000];
mySlider.minimumValue = [self unscaleValue:10];

I got all of the above to work without any problems but it could use some bulletproofing. The scaleValue: and unscaleValue: methods should check for unsupported values and I am sure sliderChange: method could be more efficient.

In terms of speed, I am sure that this is faster than pulling objects out of an array. The actual slider behavior may feel a little wonky and there isn't much control over exactly what values are available with the slider, so it may not do exactly what you want. Using really high powers seemed to make it more useful.

like image 185
MrHen Avatar answered Oct 17 '22 03:10

MrHen


You can also set a "step size" to build your slider like this :

- (void) sliderChanged:(UISlider *)slider
{
    int value = (int)[slider value];
    int stepSize = 500.0f;

    value = value - value%stepSize;

    [km setText:[NSString stringWithFormat:@"%d Km",value]];
}

Together with this solution, you can place two buttons (+ and -) to adjust your slider value :

- (void) incrementKm:(UIButton *)button 
{
    [kmSlider setValue:[kmSlider value] + 500.0f animated:YES];
    [self sliderChanged:kmSlider]; 
}

- (void) decrementKm:(UIButton *)button 
{
    [kmSlider setValue:[kmSlider value] - 500.0f animated:YES];
    [self sliderChanged:kmSlider]; 
}
like image 35
balboa Avatar answered Oct 17 '22 02:10

balboa


Here's an option if you have an variable-length array of options you want the slider to choose:

-(void)setupSlider {

    //this has to be (scale - 1) from sliderChanged selector to avoid index out of bounds error
    _sldOptionPicker.maximumValue = 99;  

    //should be zero
    _sldOptionPicker.minimumValue = 0;

    //can be any value 0 to 99
    _sldOptionPicker.value = 0;        

}

-(IBAction)sliderChanged:(id)sender {

    float scale = 100 / [optionsArray count];

    int index = (int)(_sldOptionPicker.value / scale);

    Option *mySelectedOption = (Option*)[optionsArray objectForIndex:index];

}
like image 1
Aaron Tyler Avatar answered Oct 17 '22 04:10

Aaron Tyler