Проблема
Недавно решил преукрасить интерфейс своего приложения элементом UIPickerView, но меня не совсем устроило, то, что мне предложил стандартный набор инструментов, а именно: прокрутить его так чтоб нужный элемент стал под «Selection Indicator» и потом выполнить какое-то действие. Мне нужно было чтоб по нажатию на любую строку посылалось сообщение с нужными параметрами. Поэтому я решил кастомизировать UIPickerView прикрутив к нему UITapGestureRecogniser.
Решение
Ниже я шаг за шагом распишу порядок своих действий. Я работаю с сабклассом UIViewController в котором объявлены протоколы UIPickerViewDelegate и UIPickerViewDataSource.
- Добавляем UITapGestureRecogniser в UIPickerView:
UITapGestureRecognizer *tapgesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(whereAreYouTappedOnPicker:)]; [self.pickerView addGestureRecognizer:tapgesture];
Данное действие я выполянл в функции viewDidLoad:. whereAreYouTappedOnPicker: — это селектор, который будет вызываться при нажатии на пикер и вычислять какая строка была нажата.
- Объявляем whereAreYouTappedOnPicker: в хэдере (.h файл) вашего сабкласса UIViewController.
-(void)whereAreYouTappedOnPicker:(UIGestureRecognizer *)gestureRecognizer;
- Пишем реализацию функции, которую объявили рание(делаем это в .m файле).
-(void)whereAreYouTappedOnPicker:(UIGestureRecognizer *)gestureRecognizer { //определяем координату куда нажали на UIPickerView CGPoint tapCoordinate = [gestureRecognizer locationInView:self.pickerView]; //Определяем высоту строки в UIPickerView. Я делю на 5 потому что использую стандартную высотку строки и высоту пикера CGFloat heightOfPickerRow = self.pickerView.frame.size.height/5; //объявляем переменную для хранения информации про строку на которую нажали NSInteger rowForSelectionIndicator =[self.pickerView selectedRowInComponent:0]; //Анализируем нужно ли нам покрутить пикер и поставить нажатую строку под "Selection Indicator" if (tapCoordinate.y<heightOfPickerRow) { //данная область отвечает за позицию первой строки в пикере //проверяем можем ли мы переместиться на данную строку if ([self.pickerView selectedRowInComponent:0] > 1) rowForSelectionIndicator -=2; else rowForSelectionIndicator = -1; //никаких действий не требуется } else if (tapCoordinate.y<2*heightOfPickerRow) { //данная область отвечает за позицию второй строки в пикере //проверяем можем ли мы переместиться на данную строку if ([self.pickerView selectedRowInComponent:0] > 0) rowForSelectionIndicator -=1; else rowForSelectionIndicator = -1; //никаких действий не требуется } else if (tapCoordinate.y<3*heightOfPickerRow) { //данная область отвечает за позицию третьей строки в пикере, той которая уже находиться под "Selection Indicator" //если это так, то больше ничего не нужно делать rowForSelectionIndicator = [self.pickerView selectedRowInComponent:0]; } else if (tapCoordinate.y<4*heightOfPickerRow) { //данная область отвечает за позицию четвертой строки в пикере //проверяем можем ли мы переместиться на данную строку if ([self.pickerView selectedRowInComponent:0] < ([self.pickerView numberOfRowsInComponent:0]-1)) rowForSelectionIndicator +=1; else rowForSelectionIndicator = -1; //никаких действий не требуется } else { //данная область отвечает за позицию пятой строки в пикере //проверяем можем ли мы переместиться на данную строку if ([self.pickerView selectedRowInComponent:0] < ([self.pickerView numberOfRowsInComponent:0]-2)) rowForSelectionIndicator += 2; else rowForSelectionIndicator = -1; //никаких действий не требуется } //проверям нужно ли нам выполнять какое-либо действие по нажатии на данную строку if (rowForSelectionIndicator!=-1) { //говорим пикеру прокрутиться на нужную строку //ВНИМАНИЕ - Метод didSelectRow не вызывается когда вы прокручиваете пикер на нужную строку при помощи кода. [self.pickerView selectRow:rowForSelectionIndicator inComponent:0 animated:YES]; //поэтому нам нужна другая функция [self customPickerView:self.pickerView didSelectRow:rowForSelectionIndicator inComponent:0 asResultOfTap:YES]; } }
Далее нам требуется предусмотреть два случая: когда юзер скролит пикер и когда юзер нажимает на нужную строку. Это нам нужно затем, что didSelectRow: не реагирует на прокрутку при помощи кода, здесь нам и понадобиться «другая функция»
И так, у нас будет два события:
- Когда юзер скролит пикер, то вызывается метод didSelectRow: в котором вызывается метод customPickerView: и как параметр передается индекс строки, которая находиться под «Selection Indicator».
- Когда юзер нажимает на нужную строку, то вызывается метод whereAreYouTappedOnPicker: в котором вызывается метод customPickerView: и как параметр передается индекс строки, которая была нажата.
Код метода didSelectRow:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow: (NSInteger)row inComponent:(NSInteger)component { [self customPickerView:pickerView didSelectRow:row inComponent:component asResultOfTap:NO]; }
- Объявляем функцию customPickerView: в хэдере.
-(void)customPickerView:(UIPickerView *)pickerView didSelectRow: (NSInteger)row inComponent:(NSInteger)component asResultOfTap:(bool)userTapped;
- Пишем реализацию функции customPickerView: в .m файле
- (void)customPickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component asResultOfTap:(bool)userTapped { if (userTapped) //если юзер нажал на нужную ему строку { NSLog(@"нажато на %i", row); } else //если юзер прокрутил барабан и поместил под "Selection Indicator" нужную строку { NSLog(@"выбрано %i", row); } }
Проблемы
Иногда при быстром нажатии на пикер selectedRow становится равным -1. У меня при нажатии на строку выполняется переход на новый View, поэтому этот вариант меня устраивает.