如何让UISlider以指数方式输出漂亮的四舍五入数字?

发布于 2024-09-01 07:39:31 字数 680 浏览 7 评论 0原文

我正在实现一个 UISlider,用户可以操纵它来设置距离。 我从未使用过CocoaTouch UISlider,但使用过其他框架滑块,通常有一个变量用于设置“step”和其他“helper”属性。

UISlider 的文档仅涉及最大值和最小值,并且输出始终是 6 位十进制浮点数,与“滑块旋钮”的位置呈线性关系。我想我将不得不一步步实现所需的功能。

对于用户来说,最小/最大值范围从 10 m 到 999 Km,我试图以指数方式实现这一点,这对用户来说会感觉很自然。即,用户体验到对值(无论大小)的控制感。此外,“输出”具有合理的值。诸如 10m 200m 2.5km 150 km 等值,而不是 1.2342356 m 或 108.93837756 km。

我希望前 200m 的步长大小增加 10m,然后可能增加 50m 直到 500m,然后当通过 1000 m 值时,它开始处理公里,所以步长大小 = 1 km 直到50 公里,然后可能是 25 公里步等。

无论我采取什么方式,我最终都会进行大量舍入和大量计算 大量的 if 语句和 NSString/Number 转换,每次用户移动滑块一点点。

我希望有人能给我一些灵感/数学帮助或让我意识到 解决这个问题的更精益的方法。

我的最后一个想法是用 100 个字符串值填充和数组,然后让滑块 int 值对应于一个字符串,这不是很灵活,但可行。

预先感谢您提供的任何帮助:)

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:)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

懷念過去 2024-09-08 07:39:31

快速缩放解决方案涉及三种特殊方法:

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

对滑块更改的反应类似于:

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

您可以使用以下代码进行初始化:

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

我使上述所有内容都可以正常工作,但没有任何问题,但它可以使用一些防弹措施。 scaleValue:unscaleValue: 方法应该检查不支持的值,我确信 sliderChange: 方法可能会更有效。

就速度而言,我确信这比从数组中提取对象要快。实际的滑块行为可能感觉有点奇怪,并且对滑块可用的值没有太多控制,因此它可能无法完全满足您的要求。使用真正的高功率似乎使它更有用。

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.

大海や 2024-09-08 07:39:31

您还可以设置“步长”来构建滑块,如下所示:

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

    value = value - value%stepSize;

    [km setText:[NSString stringWithFormat:@"%d Km",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]; 
}

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]; 
}
浅浅淡淡 2024-09-08 07:39:31

如果您希望滑块选择一个可变长度的选项数组,则可以选择以下选项:

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

}

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

}
请你别敷衍 2024-09-08 07:39:31

最简单的答案是使用具有不同“步长”的分段控件。根据用户选择的选项,滑块的步长是多少。我什至建议这可能是一种更用户友好的方法:)。

请随意使用 Dapp 来调整应用程序的外观,看看如何分段控制可能适合设计。

然而......您想要一种更精简的方法;)。

我开始编写所需的 10 个左右步骤,但当我意识到您可能已经得出类似的解决方案时,我停了下来。您的字符串数组想法很好,我假设您只需将滑块值转换为整数并从数组中获取相关索引?

有时,作为程序员,我们解决问题的方法太过分了。是的,字符串数组不是最“灵活”的解决方案,但它很快!而且,我认为即使是天才的数学解决方案也不像您想象的那么灵活。另外,如果您不打算很快更改这些值,并且不需要在不同的滑块上使用不同的滑块范围,那么创建一个静态数组是有意义的。

祝你好运! :)

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! :)

静水深流 2024-09-08 07:39:31
+ (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;

}

上面在实例化时加载到数组中:

设置滑块:

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

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

这甚至负责格式化用户界面的字符串,例如“200 m”或“1.5 km”并更新具有以米为单位的距离数的委托,用于使用谓词对结果进行排序时使用。

+ (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.

天生の放荡 2024-09-08 07:39:31

为了正确执行此操作,您希望滑块代表指数。例如,如果您想要 10-100000 的比例,则您希望滑块的范围为 1(10 = 10^1) 到 5 (100,000 = 10^5)。您可以将滑块上的步进设置为分数,以获得所需的精度。对于我的示例,我将使用最小值 20,000 和最大值 20,000,000 进行输出(因为这是我坐下来解决这个问题时所需要的)。因为我基本上要从最小到最大增加 1000 倍,所以我想要 10^3,所以我的滑块将为 0-3(10^0 计算为 1)。我将使用 0.001 步进,因此总共有 3000 个可能的位置。以下是完成此操作所需的代码:

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

假设您想要此函数的反函数,这样您就可以在给定一些外部输入的情况下设置滑块位置。然后您想要使用对数:

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

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

您需要调整两个函数中的 20000 以匹配您的基本金额。如果您的最大值必须由 10 以外的指数得出,请更改 10 以及最大值(指数)。如果你上升十次方,生活会更容易。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文