UISlider可以像iOS 7设置应用程序中的“文本大小”一样,捕捉到固定数量的步骤。

65

我正在尝试创建一个UISlider,它可以让你从一个数字数组中进行选择。每个滑块的位置应该是等距的,滑块应该会捕捉到每个位置,而不是在它们之间平滑地滑动。(这就是iOS 7中引入的设置> 通用> 文本大小中滑块的行为方式。)

iOS 7 Text Size Slider

我想要选择的数字是:-3, 0, 2, 4, 7, 10和12。

(我对Objective-C非常陌生,因此完整的代码示例比代码片段更有帮助。=)


4
可能是如何使用5的增量设置UISlider的重复问题。 - jrturton
3
不是这样的。因为我没有一个固定的步长为5或10,我需要能够从滑块上的7个固定值中进行选择。此外,如果我真的可以使用你提供的那个,我也不知道如何实现代码并将其改变以适应我的拟合。这可能是正确的做法,但我需要更详细的说明。 - Matias Vad
1
高赞答案可以根据您的需求进行调整。为什么不试试,如果有任何问题再问呢? - jrturton
好的,这是个好消息。但是你能否详细说明一下,也许在回答这个问题时提供一个示例代码,向我展示如何将代码适应我的需求?我应该在哪里添加委托?如何使值更改以适应我的7个不同刻度?正如我在问题中提到的,我对Objective-C还比较陌生 - 我不能只看代码然后想“啊,我可以改变这个然后...”,至少现在还不行 :) - Matias Vad
6个回答

130

其他答案有些可行,但这个方法可以让你在滑块的每个位置之间获得相同的固定间距。在这个例子中,你把滑块的位置视为一个数组的索引,该数组包含你感兴趣的实际数值。

@interface MyViewController : UIViewController {
    UISlider *slider;
    NSArray *numbers;
}
@end

@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    slider = [[UISlider alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:slider];

    // These number values represent each slider position
    numbers = @[@(-3), @(0), @(2), @(4), @(7), @(10), @(12)];
    // slider values go from 0 to the number of values in your numbers array
    NSInteger numberOfSteps = ((float)[numbers count] - 1);
    slider.maximumValue = numberOfSteps;
    slider.minimumValue = 0;

    // As the slider moves it will continously call the -valueChanged: 
    slider.continuous = YES; // NO makes it call only once you let go
    [slider addTarget:self
               action:@selector(valueChanged:)
     forControlEvents:UIControlEventValueChanged];
}
- (void)valueChanged:(UISlider *)sender {
    // round the slider position to the nearest index of the numbers array
    NSUInteger index = (NSUInteger)(slider.value + 0.5);
    [slider setValue:index animated:NO];
    NSNumber *number = numbers[index]; // <-- This numeric value you want
    NSLog(@"sliderIndex: %i", (int)index);
    NSLog(@"number: %@", number);
}

编辑:这是一个用Swift 4编写的UISlider子类,带有回调函数。

class MySliderStepper: UISlider {
    private let values: [Float]
    private var lastIndex: Int? = nil
    let callback: (Float) -> Void
    
    init(frame: CGRect, values: [Float], callback: @escaping (_ newValue: Float) -> Void) {
        self.values = values
        self.callback = callback
        super.init(frame: frame)
        self.addTarget(self, action: #selector(handleValueChange(sender:)), for: .valueChanged)
        
        let steps = values.count - 1
        self.minimumValue = 0
        self.maximumValue = Float(steps)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc func handleValueChange(sender: UISlider) {
        let newIndex = Int(sender.value + 0.5) // round up to next index
        self.setValue(Float(newIndex), animated: false) // snap to increments
        let didChange = lastIndex == nil || newIndex != lastIndex!
        if didChange {
            lastIndex = newIndex
            let actualValue = self.values[newIndex]
            self.callback(actualValue)
        }
    }
}

4
非常感谢。这正是我想要的,写得非常教人如何去做。再次感谢! - Matias Vad
所以我将"numbers"更改为一个包含超过100个数据点的数组.... 如何更改UISlider的代码,使其滑动与数据点的数量相对应?我知道"index"需要改变,但是.... :/.... 我想我会在几个小时内找到答案,但我认为其他人也希望看到关于动态数组的答案,其中滑块可以有1个索引至数千个.... :) - jsetting32
你可以通过简单更改代码以接受静态或动态数组值,获得额外的赞成票 :) - jsetting32
如果将其设置为连续值 = NO slider.continuous = NO;,它允许用户在拖动滑块时获得更平滑的操作。因为它只在用户完成拖动并将滑块移动到最接近的整数值时才评估valueChanged函数。 - jdev
@MatiasVad 我需要与问题中相同的UI,也就是小步骤垂直线,我搜索了但没有找到解决方案。 - guru
只有在用户停止拖动时,才通过对新值进行捕捉来实现更流畅的体验,即if (!self.isTracking)。这样,虚拟拇指始终跟随手指移动,只有当手势完成后,它才会跳转到最近的有效值。 - Arik Segal

9
如果您在步骤之间具有常规间距,您可以像这样使用15值:
@IBAction func timeSliderChanged(sender: UISlider) {

    let newValue = Int(sender.value/15) * 15
    sender.setValue(Float(newValue), animated: false)
}

3
这很容易实现,对于简单的情况来说是一个很好的选择。我认为被接受的答案虽然仍然是一个不错的选择,而且肯定更加可定制化,但对于大多数情况来说过于繁琐,这应该已经足够满足大多数人想要做的事情了。 - Franco

8
这对于我个人的情况有用,使用@IBDesignable:
需要均匀的整数间隔:
@IBDesignable
class SnappableSlider: UISlider {

    @IBInspectable
    var interval: Int = 1

    override init(frame: CGRect) {
        super.init(frame: frame)
        setUpSlider()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUpSlider()
    }

    private func setUpSlider() {
        addTarget(self, action: #selector(handleValueChange(sender:)), for: .valueChanged)
    }

    @objc func handleValueChange(sender: UISlider) {
        let newValue =  (sender.value / Float(interval)).rounded() * Float(interval)
        setValue(Float(newValue), animated: false)
    }
}

太棒了,你做得非常简单。 - Ilesh P
这肯定是最简单的解决方案。 - undefined

2

如果有人需要“snaping animation”(滑块停止时自动对齐),可以按照以下步骤操作:

  1. 从故事板中取消滑块的连续更新。 Storyboard

您也可以在 Swift 中执行相同的操作:slider.isContinuous = false

  1. 在您的 ViewController 中添加以下 @IBAction
   @IBAction func sliderMoved(_ slider: UISlider){

        let stepCount = 10

        let roundedCurrent = (slider.value/Float(stepCount)).rounded()
        let newValue = Int(roundedCurrent) * stepCount

        slider.setValue(Float(newValue), animated: true)
    }

我受到这个答案的启发。


2
答案基本上与这个答案关于UISlider以增量5的方式移动是相同的。
要将其修改为适用于您的情况,您需要创建一个舍入函数,仅返回您想要的值。例如,您可以执行一些简单的操作(虽然有点不正规),如下所示:
-(int)roundSliderValue:(float)x {
    if (x < -1.5) {
        return -3;
    } else if (x < 1.0) {
        return 0;
    } else if (x < 3.0) {
        return 2;
    } else if (x < 5.5) {
        return 4;
    } else if (x < 8.5) {
        return 7;
    } else if (x < 11.0) {
        return 10;
    } else {
        return 12;
    }
}

现在使用前一个帖子的答案来四舍五入值。
slider.continuous = YES;
[slider addTarget:self
      action:@selector(valueChanged:) 
      forControlEvents:UIControlEventValueChanged];

最终,实现这些更改:
-(void)valueChanged:(id)slider {
    [slider setValue:[self roundSliderValue:slider.value] animated:NO];
}

0

和PengOne的方法类似,但我进行了手动舍入以使发生的情况更清晰。

- (IBAction)sliderDidEndEditing:(UISlider *)slider {
    // default value slider will start at
    float newValue = 0.0;

    if (slider.value < -1.5) {
        newValue = -3;
    } else if (slider.value < 1) {
        newValue = 0;
    } else if (slider.value < 3) {
        newValue = 2;
    } else if (slider.value < 5.5) {
        newValue = 4;
    } else if (slider.value < 8.5) {
        newValue = 7;
    } else if (slider.value < 11) {
        newValue = 10;
    } else {
        newValue = 12;
    }
    slider.value = newValue;
}

- (IBAction)sliderValueChanged:(id)sender {
    UISlider *slider = sender;
    float newValue = 0.0;

    if (slider.value < -1.5) {
        newValue = -3;
    } else if (slider.value < 1) {
        newValue = 0;
    } else if (slider.value < 3) {
        newValue = 2;
    } else if (slider.value < 5.5) {
        newValue = 4;
    } else if (slider.value < 8.5) {
        newValue = 7;
    } else if (slider.value < 11) {
        newValue = 10;
    } else {
        newValue = 12;
    }
    // and if you have a label displaying the slider value, set it
    [yourLabel].text = [NSString stringWithFormat:@"%d", (int)newValue];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接