Статьи

Злой iOS Range Slider: Часть первая

Одним из наиболее часто используемых элементов управления iOS является ползунок. Это отличный универсальный слайдер, позволяющий быстро провести пальцем по экрану, чтобы установить значение.

Однако все становится немного сложнее, когда вы хотите создать диапазон значений. Варианты несколько ограничены. Вы должны предоставить два ползунка? Один для минимального диапазона и один для максимального диапазона? Это приносит в жертву удобство и простоту использования экрана и, как правило, не идеальный результат.

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

И вот он в действии, благодаря Мартину Ваврушу .

Обратите внимание, это не простая и быстрая работа. Чтобы правильно подобрать ползунок диапазона, потребуется немало усилий, но после его достижения вы сможете повторно использовать его в любом проекте, который вам нужен. Я бы дал себе минимум час, чтобы разобраться с этим.

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

Если это хорошо, Apple будет иметь?

Хотя Apple не предоставляет элемент управления ползунком iOS, это не значит, что вы не должны его создавать. Фактически, Apple использует слайдер диапазона в одном из своих самых больших приложений: iPhoto. Отредактируйте видео, и вы заметите, что слайдер «trim» на самом деле является слайдером диапазона. Он имеет минимальную и максимальную границы, чтобы определить, где вы хотите обрезать видео.

При создании пользовательских элементов управления для платформы iOS следует помнить, что то, что вы видите на экране, никогда не сравнится с тем, что вы испытываете при использовании пальцев. Существуют очень специфические правила юзабилити в отношении минимального размера элементов управления, и мы собираемся убедиться, что созданный нами ползунок диапазона соответствует им, и его легко использовать во всех сценариях.

Шаг 1: Основы

Мы собираемся создать новый проект и импортировать изображения, которые нам понадобятся. У нас будет четыре изображения. Вот разбивка, отображаемая как будто только что созданная в вашем любимом приложении для работы с изображениями.

Части ползунка

Части ползунка

Загрузите изображения

Вы заметите, что у нас есть ползунок полосы, выделенный цвет полосы, изображение большого пальца и изображение большого пальца, когда вы выбрали его. Самое главное, вы заметите, что у нас есть их в двух разрешениях, один для старых iPhone, и один для iPhone с дисплеем Retina.

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

Шаг 2. Создание подкласса UIControl

Этот проект по умолчанию довольно прост и имеет только контроллер навигации вместе с UITableView . Мы создадим пользовательский подкласс UIControl и поместим его в первую ячейку в табличном представлении. Cmd + N, чтобы создать новый файл. Выберите «Класс Objective-C» и нажмите «Далее». Измените UIView на UIControl и снова нажмите Next. Назовите свой класс как хотите, мой называется RangeSlider.

Теперь у вас будет базовый подкласс UIControl , в котором мы собираемся установить некоторые базовые переменные экземпляра. Есть только несколько вещей, которые мы должны set и get от нашего слайдера, мы хотим иметь возможность set и get минимальные и максимальные значения. Наконец, мы должны иметь возможность set минимальный диапазон, чтобы мы могли указать, что мы хотим, чтобы диапазон был по крайней мере х длинным. Какая польза от 3 до 3?

В заголовочном файле RangeSlider создайте определения переменных и установите свойства

 @interface RangeSlider : UIControl{ float minimumValue; float maximumValue; float minimumRange; float selectedMinimumValue; float selectedMaximumValue; } @property(nonatomic) float minimumValue; @property(nonatomic) float maximumValue; @property(nonatomic) float minimumRange; @property(nonatomic) float selectedMinimumValue; @property(nonatomic) float selectedMaximumValue; @end 

А затем в вашем главном файле, синтезировать черт из этих свойств.

 @implementation RangeSlider @synthesize minimumValue, maximumValue, minimumRange, selectedMinimumValue, selectedMaximumValue; 

Далее нам нужно установить некоторые локальные переменные, которые мы не будем показывать более широкому миру. Они просто используются для внутреннего отслеживания различных элементов нашего слайдера. Для нашего состояния большого пальца нам нужны два логических значения: _minThumbOn и _maxThumbOn . Мы будем использовать их для отслеживания текущего состояния касания слайдера. Далее нам нужны переменные UIImageView для каждого из изображений, о которых мы говорили ранее. Наконец, это значение отступа, которое позволит расположить пространство по обе стороны от изображений слайдера. Так что добавьте это в свой интерфейс заголовка.

  BOOL _maxThumbOn; BOOL _minThumbOn; float _padding; UIImageView * _minThumb; UIImageView * _maxThumb; UIImageView * _track; UIImageView * _trackBackground; 

Последний бит настройки — добавить экземпляр нашего RangeSlider в первую ячейку нашего табличного представления. В RootViewController импортируйте заголовок RangeSlider.h вверху, измените метод numberOfRowsInSection чтобы он возвращал 1, а не 0, и найдите метод cellForRowAtIndexPath . Мы собираемся RangeSlider новый экземпляр RangeSlider , установить его максимальный, минимальный и минимальный диапазон, а затем инициализировать его теми же границами, что и ячейка. Очевидно, вы можете свободно инициализировать его с любым размером кадра, который вы хотите!

Это идет ниже комментария «Настроить ячейку»

 RangeSlider *slider= [RangeSlider alloc]; slider.minimumValue = 1; slider.selectedMinimumValue = 2; slider.maximumValue = 10; slider.selectedMinimumValue = 8; slider.minimumRange = 2; [slider initWithFrame:cell.bounds]; [cell addSubview:slider]; 

Если вы сейчас создадите и запустите свой проект, вы не увидите намного больше, чем простой контроллер навигации и табличное представление. Будьте уверены, что на самом деле там есть RangeSlider управления RangeSlider . Это просто пустой вид на данный момент, хотя!

Шаг 3: Добавьте слайдер и большие пальцы

Мы собираемся начать с нашего метода initWithFrame . Он собирается установить начальное состояние и вызвать метод, который перерисовывает наш слайдер. Код для этого шага находится внутри блока.

 if(self){ // Code all in here } return self; 

Во-первых, давайте установим некоторые начальные значения состояния

 // Set the initial state _minThumbOn = false; _maxThumbOn = false; _padding = 20; // 20 is a good value 

Далее мы собираемся добавить фон дорожки и выделить изображения для элемента управления. Кадр устанавливается в соответствии с размером кадра ячейки, а не центрируется на основе центральной точки ячейки (спасибо skjolber за указание на это).

 _trackBackground = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bar-background.png"]] autorelease]; _trackBackground.frame = CGRectMake((frame.size.width - _trackBackground.frame.size.width) / 2, (frame.size.height - _trackBackground.frame.size.height) / 2, _trackBackground.frame.size.width, _trackBackground.frame.size.height); [self addSubview:_trackBackground]; _track = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bar-highlight.png"]] autorelease]; _track.frame = CGRectMake((frame.size.width - _track.frame.size.width) / 2, (frame.size.height - _track.frame.size.height) / 2, _track.frame.size.width, _track.frame.size.height); [self addSubview:_track]; 

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

Отслеживать изображения в ячейке

Далее мы собираемся добавить в два элемента управления большим пальцем, для мин и макс. Однако для их размещения нам потребуется вызвать метод, который мы еще не написали, с именем xForValue . Этот метод xForValue принимает значение ползунка и возвращает точку на оси x , в которой будет находиться значение ползунка. Прежде всего, давайте напишем код большого пальца, выбирая, где мы остановились.

 _minThumb = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"handle.png"] highlightedImage:[UIImage imageNamed:@"handle-hover.png"]] autorelease]; _minThumb.center = CGPointMake([self xForValue:selectedMinimumValue], self.frame.size.height / 2)); [self addSubview:_minThumb]; _maxThumb = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"handle.png"] highlightedImage:[UIImage imageNamed:@"handle-hover.png"]] autorelease]; _maxThumb.center = CGPointMake([self xForValue:selectedMaximumValue], self.frame.size.height / 2)); [self addSubview:_maxThumb]; 

Хорошо, давайте разберемся с этим. Код почти одинаков для каждого, только один для максимума, а другой для минимума. Мы начнем с инициализации нового изображения с выделенным состоянием тоже. Мы делаем центральную точку, используя значение x для текущего минимального или максимального значения, равное половине значения высоты нашего кадра. Затем мы добавляем изображения в наш основной вид управления.

Это не будет работать в его текущем состоянии, так как некоторые красные флаги ошибок будут сообщать вам. Нам нужно определить и создать наш метод xForValue .

Шаг 4: Давайте разберемся с математикой

В верхней части нашей реализации RangeSlider мы собираемся добавить область для определения частных методов — так как этот метод не должен быть обнародован. Над @implementation добавьте новый интерфейс закрытых методов с определением метода для нашего метода xForValue .

  @interface RangeSlider (PrivateMethods) -(float)xForValue:(float)value; @end с  @interface RangeSlider (PrivateMethods) -(float)xForValue:(float)value; @end с  @interface RangeSlider (PrivateMethods) -(float)xForValue:(float)value; @end 

И затем после метода initWithFrame мы собираемся создать xForValue . Теперь код здесь не имеет смысла без диаграммы. Итак, я сделал диаграмму.

Это помогает понять?

Как только вы сломаете это, это просто, верно? Значение x равно ширине ползунка (ширина кадра минус заполнение каждой стороны), умноженное на процент того, насколько далеко ползунок находится между минимальным и максимальным значениями, добавленного к одной партии заполнения (чтобы переместить его один отступ слева).

Таким образом, если ширина ползунка равна полной ширине минус 2 *, то мы можем назвать это a . И процент от значения b , с отступом p, тогда мы получим следующее.

  х = а * б + р 

Замените a значениями width и padding — self.frame.size.width и _padding , а p — _padding .

  x = (self.frame.size.width - (_ padding * 2)) * b + _padding 

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

  b = (значение - минимальное значение) / (максимальное значение - минимальное значение) 

Оставьте это в нашем предыдущем значении, и мы получим:

  x = (self.frame.size.width - (_ padding * 2)) * ((значение - минимальное значение) / (MaximumValue - минимальное значение)) + _ заполнение 

Теперь поместите это в определение нашего метода, и мы получим это!

 -(float)xForValue:(float)value{ return (self.frame.size.width-(_padding*2))*((value - minimumValue) / (maximumValue - minimumValue))+_padding; } с -(float)xForValue:(float)value{ return (self.frame.size.width-(_padding*2))*((value - minimumValue) / (maximumValue - minimumValue))+_padding; } 

И, наконец, построить и запустить! Посмотрите на эти идеально расположенные пальцы …

С ползунком большие пальцы на месте

Шаг 5: Время переместить

Пришло время начать смотреть на то, как мы взаимодействуем с сенсорными событиями. Здесь мы собираемся реализовать три метода: beginTrackingWithTouch , endTrackingWithTouch и continueTrackingWithTouch . Вот наш метод отслеживания начала.

 -(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{ CGPoint touchPoint = [touch locationInView:self]; if(CGRectContainsPoint(_minThumb.frame, touchPoint)){ _minThumbOn = true; }else if(CGRectContainsPoint(_maxThumb.frame, touchPoint)){ _maxThumbOn = true; } return YES; } 

Как видите, мы начинаем с получения текущей точки касания. Затем мы проверяем, содержит ли рамка большого пальца нашу точку соприкосновения; в основном спрашивая, касались ли мы одного из больших пальцев, и если да, то мы устанавливаем для связанной переменной _minThumbOn или _maxThumbOn значение true, поэтому, когда мы продолжаем отслеживать касание, мы знаем, какой большой палец перемещать!

Наш метод отслеживания конца просто гарантирует, что переменные max и min thumbOn имеют значение false.

 -(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{ _minThumbOn = false; _maxThumbOn = false; } 

Метод продолжения касания — вот где происходит магия. Здесь мы перемещаем большой палец в правильное место!

 -(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{ if(!_minThumbOn && !_maxThumbOn){ return YES; } CGPoint touchPoint = [touch locationInView:self]; if(_minThumbOn){ _minThumb.center = CGPointMake(MAX([self xForValue:minimumValue],MIN(touchPoint.x, [self xForValue:selectedMaximumValue - minimumRange])), _minThumb.center.y); } if(_maxThumbOn){ _maxThumb.center = CGPointMake(MIN([self xForValue:maximumValue], MAX(touchPoint.x, [self xForValue:selectedMinimumValue + minimumRange])), _maxThumb.center.y); } [self setNeedsDisplay]; return YES; } 

Мы начинаем с возврата YES, если мы не касаемся ползунка большого пальца. Нет смысла делать дополнительную работу, а? После этого мы настраиваем центр вида большого пальца на положение x новой точки большого пальца, но не в том случае, если оно находится за пределами значений максимального диапазона или слишком близко к другому ползунку (повторно используя наш метод xForValue из более раннего). Операторы if просто отличаются между тем, хотим ли мы переместить максимальный или минимальный большой палец.

 _minThumb.center = CGPointMake(MAX([self xForValue:minimumValue],MIN(touchPoint.x, [self xForValue:selectedMaximumValue - minimumRange])), _minThumb.center.y); 

Оператор MIN говорит, что мы хотим получить самую низкую точку касания или самую высокую точку, к которой мы можем перейти, пока большой палец не окажется слишком близко к максимальному большому. В заявлении MAX говорится, что мы хотим либо точку касания, которую мы определили, либо самую низкую позицию, которой может быть x, прежде чем он minimumValue в наш отступ: x для нашего minimumValue .

Наконец, мы вызываем метод setNeedsDisplay элементов управления, чтобы перерисовать базовый UIView и отобразить ход.

Если вы сейчас это создадите и запустите, вы увидите, что можете выбрать большие пальцы и запускать их вверх и вниз. Они также пойдут так далеко, как определено в minimumRange (2 в нашем примере).

Это конец первой части

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