Ответ 1
Прежде всего, спасибо за подачу ошибки о UIDatePicker
и еврейском календаре.:)
ИЗМЕНИТЬ Теперь, когда iOS 6 был выпущен, вы обнаружите, что UIDatePicker
теперь корректно работает с еврейским календарем, делая код ниже ненужным. Однако я оставлю это для потомков.
Как вы обнаружили, создание подходящего выбора даты - сложная проблема, потому что для прикрытия используются байкилии странных краевых случаев. Еврейский календарь особенно странный в этом отношении, имеющий промежуточный месяц (Adar I), тогда как большая часть западной цивилизации используется для календаря, который только добавляет дополнительный день примерно раз в 4 года.
Таким образом, создание минимального сборщика дат на иврите не слишком сложное, предполагая, что вы готовы отказаться от некоторых тонкостей, которые предлагает UIDatePicker
. Поэтому давайте упростим:
@interface HPDatePicker : UIPickerView
@property (nonatomic, retain) NSDate *date;
- (void)setDate:(NSDate *)date animated:(BOOL)animated;
@end
Мы просто перейдем к подклассу UIPickerView
и добавим поддержку свойства date
. Мы проигнорируем minimumDate
, maximumDate
, locale
, calendar
, timeZone
и все другие забавные свойства, которые предоставляет UIDatePicker
. Это упростит нашу работу.
Реализация начнется с расширения класса:
@interface HPDatePicker () <UIPickerViewDelegate, UIPickerViewDataSource>
@end
Просто чтобы скрыть, что HPDatePicker
является его собственным делегатом и источником данных.
Далее мы определим пару удобных констант:
#define LARGE_NUMBER_OF_ROWS 10000
#define MONTH_COMPONENT 0
#define DAY_COMPONENT 1
#define YEAR_COMPONENT 2
Здесь вы можете видеть, что мы собираемся упорядочить порядок блоков календаря. Другими словами, это средство выбора даты всегда будет отображать вещи как Месяц-Год-Год, независимо от настроек или настроек языка, которые могут быть у пользователя. Поэтому, если вы используете это в локали, где формат по умолчанию будет нужен "Day-Month-Year", то это слишком плохо. Для этого простого примера этого будет достаточно.
Теперь мы начинаем реализацию:
@implementation HPDatePicker {
NSCalendar *hebrewCalendar;
NSDateFormatter *formatter;
NSRange maxDayRange;
NSRange maxMonthRange;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setDelegate:self];
[self setDataSource:self];
[self setShowsSelectionIndicator:YES];
hebrewCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar];
formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:hebrewCalendar];
maxDayRange = [hebrewCalendar maximumRangeOfUnit:NSDayCalendarUnit];
maxMonthRange = [hebrewCalendar maximumRangeOfUnit:NSMonthCalendarUnit];
[self setDate:[NSDate date]];
}
return self;
}
- (void)dealloc {
[hebrewCalendar release];
[formatter release];
[super dealloc];
}
Мы переопределяем назначенный инициализатор, чтобы выполнить некоторые настройки для нас. Мы устанавливаем делегат и источник данных сами, показываем индикатор выбора и создаем объект календаря на иврите. Мы также создаем NSDateFormatter
и говорим, что он должен отформатировать NSDates
в соответствии с еврейским календарем. Мы также вытаскиваем пару объектов NSRange
и кешируем их как ivars, поэтому нам не нужно постоянно искать вещи. Наконец, мы инициализируем его с текущей датой.
Ниже приведены реализации открытых методов:
- (void)setDate:(NSDate *)date {
[self setDate:date animated:NO];
}
-setDate:
просто переходит к другому методу
- (NSDate *)date {
NSDateComponents *c = [self selectedDateComponents];
return [hebrewCalendar dateFromComponents:c];
}
Извлеките NSDateComponents
, представляющий все, что выбрано в данный момент, превратите его в NSDate
и верните это.
- (void)setDate:(NSDate *)date animated:(BOOL)animated {
NSInteger units = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *components = [hebrewCalendar components:units fromDate:date];
{
NSInteger yearRow = [components year] - 1;
[self selectRow:yearRow inComponent:YEAR_COMPONENT animated:animated];
}
{
NSInteger middle = floor([self pickerView:self numberOfRowsInComponent:MONTH_COMPONENT] / 2);
NSInteger startOfPhase = middle - (middle % maxMonthRange.length) - maxMonthRange.location;
NSInteger monthRow = startOfPhase + [components month];
[self selectRow:monthRow inComponent:MONTH_COMPONENT animated:animated];
}
{
NSInteger middle = floor([self pickerView:self numberOfRowsInComponent:DAY_COMPONENT] / 2);
NSInteger startOfPhase = middle - (middle % maxDayRange.length) - maxDayRange.location;
NSInteger dayRow = startOfPhase + [components day];
[self selectRow:dayRow inComponent:DAY_COMPONENT animated:animated];
}
}
И здесь начинается забавное вещание.
Сначала мы возьмем дату, которую нам дали, и попросим календарь на иврите разбить его на объект компонентов даты. Если я дам ему NSDate
, который соответствует дате gregorian от 4 апреля 2012 года, тогда ивритский календарь даст мне объект NSDateComponents
, который соответствует 12 Nisan 5772, что в тот же день, что и 4 апреля 2012 года.
Основываясь на этой информации, я определяю, какую строку выбрать в каждом модуле, и выберите ее. Дело года просто. Я просто вычитаю один (строки основаны на нуле, но годы основаны на 1).
В течение нескольких месяцев я выбираю середину столбца строк, выясняю, где начинается эта последовательность, и добавьте к ней номер месяца. То же самое со днями.
Реализации базовых методов <UIPickerViewDataSource>
довольно тривиальны. Мы показываем 3 компонента, каждый из которых имеет 10 000 строк.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 3;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return LARGE_NUMBER_OF_ROWS;
}
Получение того, что выбрано в настоящий момент, довольно просто. Я получаю выбранную строку в каждом компоненте и добавляю 1 (в случае NSYearCalendarUnit
) или выполняю небольшую операцию mod для учета повторяющегося характера других блоков календаря.
- (NSDateComponents *)selectedDateComponents {
NSDateComponents *c = [[NSDateComponents alloc] init];
[c setYear:[self selectedRowInComponent:YEAR_COMPONENT] + 1];
NSInteger monthRow = [self selectedRowInComponent:MONTH_COMPONENT];
[c setMonth:(monthRow % maxMonthRange.length) + maxMonthRange.location];
NSInteger dayRow = [self selectedRowInComponent:DAY_COMPONENT];
[c setDay:(dayRow % maxDayRange.length) + maxDayRange.location];
return [c autorelease];
}
Наконец, мне нужны строки для отображения в пользовательском интерфейсе:
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *format = nil;
NSDateComponents *c = [[NSDateComponents alloc] init];
if (component == YEAR_COMPONENT) {
format = @"y";
[c setYear:row+1];
[c setMonth:1];
[c setDay:1];
} else if (component == MONTH_COMPONENT) {
format = @"MMMM";
[c setYear:5774];
[c setMonth:(row % maxMonthRange.length) + maxMonthRange.location];
[c setDay:1];
} else if (component == DAY_COMPONENT) {
format = @"d";
[c setYear:5774];
[c setMonth:1];
[c setDay:(row % maxDayRange.length) + maxDayRange.location];
}
NSDate *d = [hebrewCalendar dateFromComponents:c];
[c release];
[formatter setDateFormat:format];
NSString *title = [formatter stringFromDate:d];
return title;
}
@end
Здесь ситуация немного сложнее. К сожалению для нас NSDateFormatter
может форматировать данные только при фактическом NSDate
. Я не могу просто сказать "здесь 6" и надеюсь вернуться "Адар I". Таким образом, я должен построить искусственную дату, которая имеет значение, которое я хочу в блоке, о котором я забочусь.
В случае лет, это довольно просто. Просто создайте компоненты даты для этого года на Tishri 1, и я хорошо.
В течение нескольких месяцев я должен убедиться, что год - високосный год. Поступая таким образом, я могу гарантировать, что имена месяцев всегда будут "Adar I" и "Adar II", независимо от того, будет ли текущий год високосным годом или нет.
В течение нескольких дней я выбрал произвольный год, потому что у каждого Тишри 30 дней (и ни один месяц в еврейском календаре не превышает 30 дней).
Как только мы построим соответствующий объект компонентов даты, мы можем быстро превратить его в NSDate
с нашим hebrewCalendar
ivar, установить строку формата в форматировании даты только для создания строк для единицы, которая нас интересует, и сгенерировать строку.
Предполагая, что вы сделали все это правильно, вы получите следующее:
Некоторые примечания:
-
Я отказался от реализации
-pickerView:didSelectRow:inComponent:
. Вам решать, как вы хотите сообщить, что выбранная дата изменилась. -
Это не обрабатывает недействительные даты. Например, вы можете захотеть посеять "Adar I", если выбранный год не является високосным годом. Для этого потребуется использовать
-pickerView:viewForRow:inComponent:reusingView:
вместо методаtitleForRow:
. -
UIDatePicker
выделит текущую дату синим цветом. Опять же, вам нужно будет вернуть пользовательский вид вместо строки, чтобы сделать это. -
Ваш выборщик будет иметь черную рамку, потому что это
UIPickerView
. ТолькоUIDatePickers
получает синий цвет. -
Компоненты представления выбора будут охватывать всю его ширину. Если вы хотите, чтобы все было более естественно, вам нужно переопределить
-pickerView:widthForComponent:
, чтобы вернуть нормальное значение для соответствующего компонента. Это может включать жесткое кодирование значения или генерирование всех строк, их размер и выбор самого большого (плюс некоторое дополнение). -
Как отмечалось ранее, это всегда отображает вещи в порядке Month-Day-Year. Придание этой динамике текущему языку было бы немного сложнее. Вам нужно будет получить строку
@"d MMMM y"
, локализованную в текущей локали (подсказка: посмотрите на методы класса наNSDateFormatter
для этого), а затем проанализируйте ее, чтобы выяснить, что такое заказ.