iOS how to make slider stop at discrete points
Asked Answered
S

5

47

I would like to make a slider stop at discrete points that represent integers on a timeline. What's the best way to do this? I don't want any values in between. It would be great if the slider could "snap" to position at each discrete point as well.

Stela answered 16/8, 2011 at 18:46 Comment(0)
G
41

To make the slider "stick" at specific points, your viewcontroller should, in the valueChanged method linked to from the slider, determine the appropriate rounded from the slider's value and then use setValue: animated: to move the slider to the appropriate place. So, if your slider goes from 0 to 2, and the user changes it to 0.75, you assume this should be 1 and set the slider value to that.

Glazunov answered 16/8, 2011 at 19:23 Comment(2)
As Nathan says below, use 'Touch up inside' because it will flicker while dragging if you use valueChangedSpiderwort
That only applies if the slider is continuous, but yes, in that case it is a better solutionGlazunov
S
72

The steps that I took here were pretty much the same as stated in jrturton's answer but I found that the slider would sort of lag behind my movements quite noticeably. Here is how I did this:

Put the slider into the view in Interface Builder. Set the min/max values of the slider. (I used 0 and 5)

In the .h file:

@property (strong, nonatomic) IBOutlet UISlider *mySlider;
- (IBAction)sliderChanged:(id)sender;

In the .m file:

- (IBAction)sliderChanged:(id)sender 
{
    int sliderValue;
    sliderValue = lroundf(mySlider.value);
    [mySlider setValue:sliderValue animated:YES];
}

After this in Interface Builder I hooked up the 'Touch Up Inside' event for the slider to File's Owner, rather than 'Value Changed'. Now it allows me to smoothly move the slider and snaps to each whole number when my finger is lifted.

Thanks @jrturton!

UPDATE - Swift:

@IBOutlet var mySlider: UISlider!

@IBAction func sliderMoved(sender: UISlider) {
    sender.setValue(Float(lroundf(mySlider.value)), animated: true)
}

Also if there is any confusion on hooking things up in the storyboard I have uploaded a quick example to github: https://github.com/nathandries/StickySlider

Sullage answered 14/3, 2012 at 2:26 Comment(4)
Nice but it'll round down. lroundf will round to nearest integer.Endymion
I'd also use 'Touch Up Outside'Spiderwort
You can replace mySlider.value with sender.value so you don't need any external reference (in case you have an array of sliders or something like that)Kalina
You still need valueChanged if you wish to visually mark changes.Permute
G
41

To make the slider "stick" at specific points, your viewcontroller should, in the valueChanged method linked to from the slider, determine the appropriate rounded from the slider's value and then use setValue: animated: to move the slider to the appropriate place. So, if your slider goes from 0 to 2, and the user changes it to 0.75, you assume this should be 1 and set the slider value to that.

Glazunov answered 16/8, 2011 at 19:23 Comment(2)
As Nathan says below, use 'Touch up inside' because it will flicker while dragging if you use valueChangedSpiderwort
That only applies if the slider is continuous, but yes, in that case it is a better solutionGlazunov
G
6

What I did for this is first set an "output" variable of the current slider value to an integer (its a float by default). Then set the output number as the current value of the slider:

int output = (int)mySlider.value;
mySlider.value = output;

This will set it to move in increments of 1 integers. To make it move in a specific range of numbers, say for example, in 5s, modify your output value with the following formula. Add this between the first two lines above:

int output = (int)mySlider.value;
int newValue = 5 * floor((output/5)+0.5);
mySlider.value = newValue;

Now your slider "jumps" to multiples of 5 as you move it.

Groot answered 12/3, 2013 at 16:56 Comment(0)
S
4

I did as Nathan suggested, but I also want to update an associated UILabel displaying the current value in real time, so here is what I did:

- (IBAction) countdownSliderChanged:(id)sender
{
    // Action Hooked to 'Value Changed' (continuous)

    // Update label (to rounded value)

    CGFloat value = [_countdownSlider value];

    CGFloat roundValue = roundf(value);

    [_countdownLabel setText:[NSString stringWithFormat:@" %2.0f 秒", roundValue]];
}


- (IBAction) countdownSliderFinishedEditing:(id)sender
{
    // Action Hooked to 'Touch Up Inside' (when user releases knob)

    // Adjust knob (to rounded value)

    CGFloat value = [_countdownSlider value];

    CGFloat roundValue = roundf(value);

    if (value != roundValue) {
        // Almost 100% of the time - Adjust:

        [_countdownSlider setValue:roundValue];
    }
}

The drawback, of course, is that it takes two actions (methods) per slider.

Shechem answered 16/5, 2013 at 2:53 Comment(0)
P
0

Swift version with ValueChanged and TouchUpInside.

EDIT: Actually you should hook up 3 events in this case:

yourSlider.addTarget(self, action: #selector(presetNumberSliderTouchUp), for: [.touchUpInside, .touchUpOutside, .touchCancel])

I've just pasted my code, but you can easily see how it's done.

private func setSizesSliderValue(pn: Int, slider: UISlider, setSliderValue: Bool)
{
    if setSliderValue
    {
        slider.setValue(Float(pn), animated: true)
    }

    masterPresetInfoLabel.text = String(
        format: TexturingViewController.createAndSendPresetNumberNofiticationTemplate,
        self.currentPresetNumber.name.uppercased(),
        String(self.currentPresetNumber.currentUserIndexHumanFriendly)
    )
}

@objc func presetNumberSliderTouchUp(_ sender: Any)
{
    guard let slider = sender as? NormalSlider else{
        return
    }

    setupSliderChangedValuesGeneric(slider: slider, setSliderValue: true)
}

private func setupSliderChangedValuesGeneric(slider: NormalSlider, setSliderValue: Bool)
{
    let rounded = roundf((Float(slider.value) / Float(presetNumberStep))) * Float(presetNumberStep)

    // Set new preset number value
    self.currentPresetNumber.current = Int(rounded)
    setSizesSliderValue(pn: Int(rounded), slider: slider, setSliderValue: setSliderValue)
}

@IBAction func presetNumberChanged(_ sender: Any)
{
    guard let slider = sender as? NormalSlider else{
        return
    }

    setupSliderChangedValuesGeneric(slider: slider, setSliderValue: false)
}
Permute answered 17/11, 2019 at 10:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.