UISlider, который привязывается к фиксированному количеству шагов (например, размер текста в приложении настроек iOS 7)

Я пытаюсь создать UISlider, который позволяет вам выбирать из массива чисел. Каждое положение ползунка должно быть равноудалено, и ползунок должен привязываться к каждому положению, а не плавно перемещаться между ними. (Это поведение ползунка в Settings> General> Text Size, которое было введено в iOS 7.)

Ползунок размера текста iOS 7

Я хочу выбрать из следующих чисел: -3, 0, 2, 4, 7, 10, and 12.

(Я новичок в Objective-C, поэтому полный пример кода был бы намного полезнее, чем фрагмент кода. =)


person Matias Vad    schedule 21.11.2011    source источник
comment
возможный дубликат Как использовать UISlider с шагом 5   -  person jrturton    schedule 22.11.2011
comment
Нет, это не так. Поскольку у меня нет фиксированного шага 5 или 10. Мне нужна возможность выбирать из 7 фиксированных значений на ползунке. Кроме того, если я действительно могу использовать тот, с которым вы связались, я не знаю, как реализовать код и изменить его для моей установки. Возможно, это правильный способ сделать это, но мне нужно пояснение.   -  person Matias Vad    schedule 22.11.2011
comment
Ответ на связанный вопрос, получивший большое количество голосов, может быть адаптирован к вашим потребностям. Почему бы вам не попробовать это, а затем снова спросить, есть ли у вас с этим проблемы?   -  person jrturton    schedule 22.11.2011
comment
Хорошо, это хорошие новости. Но не могли бы вы подробнее рассказать мне, возможно, в ответ на этот вопрос, предоставив образец кода, показывающий мне, как адаптировать код к моим потребностям? Куда мне добавить делегата? И как мне изменить значение, чтобы оно соответствовало моим 7 различным тикам? Как я уже упоминал в своем вопросе, я новичок в Objective-C - я не могу просто взглянуть на код и подумать А-а, я могу изменить это, а затем ..., пока :)   -  person Matias Vad    schedule 22.11.2011


Ответы (6)


Некоторые другие ответы работают, но это даст вам одинаковое фиксированное пространство между каждой позицией в вашем слайдере. В этом примере вы обрабатываете позиции ползунка как индексы для массива, который содержит фактические числовые значения, которые вас интересуют.

@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)
        }
    }
}
person matsr    schedule 21.11.2011
comment
Большое Вам спасибо. Это именно то, что я хотел и написал, чтобы научить меня это делать. Еще раз спасибо! - person Matias Vad; 22.11.2011
comment
Итак, я изменил числа на массив с более чем 100 точками данных .... Как мне изменить код для UISlider, чтобы он скользил в соответствии с количеством имеющихся точек данных? Я понимаю, что этот индекс необходимо изменить, но ....: / .... Я полагаю, что найду ответ в течение нескольких часов, но я думаю, что некоторые другие хотели бы увидеть ответ для ДИНАМИЧЕСКОГО массива, где слайдер может иметь 1 индекс до тысяч .... :) - person jsetting32; 26.06.2013
comment
Вы получаете дополнительный голос за простое изменение вашего кода, чтобы он принимал значения статического или динамического массива :) - person jsetting32; 26.06.2013
comment
Если вы установите для него непрерывные значения = NO slider.continuous = NO;, это позволит пользователю более плавно выполнять действия при перетаскивании ползунка. Поскольку он оценивает функцию valueChanged только тогда, когда пользователь завершает перетаскивание ползунка к ближайшему целочисленному значению. - person jdev; 29.12.2015
comment
@MatiasVad Мне нужен такой же пользовательский интерфейс, как и в вопросе, я имею в виду вертикальную линию с маленькими шагами, я искал, но не нашел для нее решения. - person guru; 11.07.2019
comment
Более плавное восприятие может быть выполнено путем привязки к новому значению, только если (! Self.isTracking). то есть только тогда, когда пользователь перестанет принимать наркотики. Таким образом, виртуальный большой палец всегда следует за пальцем и только после выполнения жеста переходит к ближайшему допустимому значению. - person Arik Segal; 03.11.2019

Если у вас есть регулярные промежутки между шагами, вы можете использовать его так для значения 15:

@IBAction func timeSliderChanged(sender: UISlider) {

    let newValue = Int(sender.value/15) * 15
    sender.setValue(Float(newValue), animated: false)
}
person Bartłomiej Semańczyk    schedule 07.01.2016
comment
Это легко реализовать и отличный вариант для простых случаев. Я думаю, что принятый ответ (хотя он все еще является хорошим вариантом и, безусловно, более настраиваемым) является излишним для большинства случаев, и этого должно быть достаточно для того, что хочет делать большинство людей. - person Franco; 08.01.2019

Это сработало для моего конкретного случая, используя @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)
    }
}
person taykay08    schedule 09.01.2019
comment
Потрясающе ???????? вы сделали это очень просто. - person Ilesh P; 30.10.2020

Ответ практически такой же в этом ответе на 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];
}
person PengOne    schedule 21.11.2011

Если кому-то также нужна анимация привязки, можно сделать следующее:

  1. Снимите флажок с непрерывного обновления ползунка в раскадровке. Раскадровка

Вы можете сделать то же самое в 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)
    }

Меня вдохновил этот ответ

person zoha131    schedule 29.05.2020

Такой же подход, как и в 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];
}
person thedonniew    schedule 14.11.2014