How to make UISlider output nice rounded numbers exponentially?

折月煮酒 提交于 2019-12-03 13:28:24

The easiest answer is to use a Segmented Control with the different 'step sizes'. Depending on what option the user selects, is what step size your slider will have. I'd even recommend that this is perhaps a more user friendly way to approach it :).

Feel free to use Dapp to play with the look of your app and see how a segmented control might fit with the design.

However... you wanted a leaner approach ;).

I started writing the 10 or so steps needed, but I stopped when I realised you had probably already come to a similar solution. Your string array idea is fine, I assume you will simply convert the slider value to an integer and grab the relevant index from the array?

Sometimes as programmers we go too far with our approaches to a problem. Yes, a string array isn't the most 'flexible' solution but it is fast! And, I'd argue that even a genius mathematical solution isn't as flexible as you may think. Also, if you don't plan on changing the values anytime soon and don't need different slider ranges on different sliders, then it makes sense to just create a static array.

Good luck! :)

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.

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]; 
}

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];

}
+ (NSArray*) getSliderNumbers {

    NSArray *sliderNumbers = [NSArray arrayWithObjects:@"10",
                          @"20",
                          @"30",
                          @"40",
                          @"50",
                          @"60",
                          @"70",
                          @"80",
                          @"90",
                          @"100",
                          @"150",
                          @"200",
                          @"250",
                          @"300",
                          @"350",
                          @"400",
                          @"450",
                          @"500",
                          @"600",
                          @"700",
                          @"800",
                          @"900",
                          @"1",
                          @"1.5",
                          @"2.0",
                          @"2.5",
                          @"3.0",
                          @"3.5",
                          @"4",
                          @"4.5",
                          @"5",
                          @"5.5",
                          @"6",
                          @"6.5",
                          @"7",
                          @"7.5",
                          @"8",
                          @"8.5",
                          @"9",
                          @"9.5",
                          @"10",
                          @"15",
                          @"20",
                          @"25",
                          @"30",
                          @"35",
                          @"40",
                          @"45",
                          @"50",
                          @"55",
                          @"60",
                          @"65",
                          @"70",
                          @"75",
                          @"80",
                          @"85",
                          @"90",
                          @"95",
                          @"100",
                          @"200",
                          @"300",
                          @"400",
                          @"500",
                          @"600",
                          @"700",
                          @"800",
                          @"900",
                          nil];
    return sliderNumbers;

}

above is loaded into an array upon instantiation:

Set up the slider:

    customSlider.minimumValue = 0.0f;
    customSlider.maximumValue = (CGFloat)[sliderNumbers count] - 1;
    customSlider.continuous = YES;
    customSlider.value = customSlider.maximumValue;

The method called on UIControlEventValueChanged

- (void) sliderMove:(UISlider*) theSlider {

    NSInteger numberLookup = lroundf([theSlider value]);

    NSString *distanceString = [sliderNumbers objectAtIndex:numberLookup];
    CGFloat distanceInMeters;

    if (numberLookup > 21) {

        [self.indicator.indicatorLabel setText:[NSString stringWithFormat:@"%@ km", distanceString]];       
        distanceInMeters = [distanceString floatValue] * 1000;
    } else {

        [self.indicator.indicatorLabel setText:[NSString stringWithFormat:@"%@ m", distanceString]];
        distanceInMeters = [distanceString floatValue];
    }

    if (oldDistanceInMeters != distanceInMeters) {

        [self.delegate distanceSliderChanged:distanceInMeters];
        oldDistanceInMeters = distanceInMeters;
    }
}

This even takes care of formatting the string for the user interface e.g. "200 m" or "1.5 km" and updates the delegate with the distance number in meters for using when sorting my results with a predicate.

In order to do this right, you want your slider to represent exponents. So for instance if you want a scale of 10-100000, you'd want your slider to have a range of 1(10 = 10^1) through 5 (100,000 = 10^5). You can set the stepping on the slider to a fraction in order to give you the precision you want. For my example, I'll be using a min of 20,000 and a max of 20,000,000 for output (because that's what I needed when I sat down to figure this out). Since I am basically going to increase by 1000 times from min to max I want 10^3, so my slider is going to be 0-3 (10^0 evaluates to 1). I'll use .001 stepping so there are a total of 3000 possible positions. Here's the code you'll need to accomplish this:

  $("#amount-slider").slider({
   value:20000,
   min: 0,
   max: 3,
   step:.001,
   slide: function(event, ui) {
    $("#amount").val(Math.pow(10, ui.value)*20000);
   }
  });

Suppose you want the inverse of this function, so you can set the slider position given some external input. Then you want to use logarithm:

  var amtVal = parseFloat($("#amount").val());

  $('#amount-slider').slider('value', ((Math.log(amtVal/20000)/Math.log(10))));

You'll need to adjust the 20000 in both functions to match your base amount. If your max must be derived by exponents of something other than 10, change 10 as well as the max (exponent). Life will be easier if you go up by powers of ten.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!