Вопрос по uipickerview, objective-c, uidatepicker, ios – Как переписать компонент UIDatePicker?

15

Я заметил, чтоUIDatePicker не работает с NSHebrewCalendar в iOS 5.0 или 5.1. Я решил попробовать написать свою собственную. Я запутался в том, как заполнять данные и как поддерживать метки для дат рациональным и эффективным способом памяти.

Сколько строк на самом деле в каждом компоненте? Когда строки «перезагружаются»? с новыми ярлыками?

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

Ваш Ответ

2   ответа
1

UIPickerView работает как UITableView в том смысле, что вы указываете другой класс как источник данных / делегат, и он получает его данные, вызывая методы этого класса (которые должны соответствовать протоколу UIPickerViewDelegate / DataSource).

Итак, вам нужно создать новый класс, который является подклассом UIPickerView, затем в методе initWithFrame установить его в качестве собственного источника данных / делегата и затем реализовать методы.

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

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

https://github.com/nicklockwood/CountryPicker

Что касается вопроса памяти, UIPickerView загружает только те метки, которые видны на экране, и перерабатывает их, когда они прокручиваются снизу и возвращаются вверх, вызывая вашего делегата снова каждый раз, когда ему нужно отобразить новую строку. Это очень эффективно использует память, поскольку на экране будет отображаться только несколько ярлыков.

Error: User Rate Limit Exceeded Moshe
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
35

UIDatePicker и еврейский календарь. :)

EDIT Теперь, когда iOS 6 выпущена, вы обнаружите, чтоUIDatePicker теперь корректно работает с еврейским календарем, что делает ненужным приведенный ниже код. Однако я оставлю это для потомков.

Как вы обнаружили, создание функционирующего средства выбора даты - сложная проблема, потому что нужно решить несколько странных крайних случаев. Еврейский календарь особенно странен в этом отношении, имеявставленныйmonth (Адар 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 обеспечивает. Это сделает нашу работуmuch проще.

Реализация будет начинаться с расширения класса:

@interface HPDatePicker () <UIPickerViewDelegate, UIPickerViewDataSource>

@end

Просто чтобы скрыть что тоHPDatePicker это собственный делегат и источник данных.

Далее мы определим пару удобных констант:

#define LARGE_NUMBER_OF_ROWS 10000

#define MONTH_COMPONENT 0
#define DAY_COMPONENT 1
#define YEAR_COMPONENT 2

Здесь вы можете видеть, что мы собираемся жестко закодировать порядок календарных единиц. Другими словами, этот инструмент выбора даты будет всегда отображать вещи как месяц-день-год, независимо от каких-либо настроек или настроек локали, которые может иметь пользователь. Поэтому, если вы используете это в локали, где формат по умолчанию будет хотеть «День-Месяц-Год», то это тоже плохо. Для этого простого примера этого будет достаточно.

Теперь приступим к реализации:

@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 что соответствует григорианской дате 4 апреля 2012 года, тогда еврейский календарь даст мнеNSDateComponents объект, соответствующий 12 нисан 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), или сделайте небольшую модификацию, чтобы учесть повторяющуюся природу других календарных единиц.

- (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". Таким образом, я должен построить искусственную дату, которая имеет значение, которое я хочу в единице, о которой я забочусь.

В случае лет это довольно просто. Просто создайте компоненты даты для этого года на Тишри 1, и я рад.

В течение нескольких месяцев я должен быть уверен, что это високосный год. Таким образом, я могу гарантировать, что названия месяцев всегда будут "Adar I" и «Adar II», независимо от того, является ли текущий год високосным или нет.

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

После того, как мы создали соответствующий объект компонентов даты, мы можем быстро превратить его вNSDate с нашимhebrewCalendar ivar, установите строку форматирования в форматере даты так, чтобы она производила строки только для того модуля, который нам нужен, и генерируйте строку.

Предполагая, что вы все сделали правильно, вы получите следующее:

custom hebrew date picker

Некоторые заметки:

I've left out the implementation of -pickerView:didSelectRow:inComponent:. It's up to you to figure out how you want to notify that the selected date changed.

This doesn't handle graying out invalid dates. For example, you might want to consider graying out "Adar I" if the currently selected year isn't a leap year. This would require using -pickerView:viewForRow:inComponent:reusingView: instead of the titleForRow: method.

UIDatePicker will highlight the current date in blue. Again, you'd have to return a custom view instead of a string to do that.

Your date picker will have a blackish bezel, because it is a UIPickerView. Only UIDatePickers get the blueish one.

The components of the picker view will span its entire width. If you want things to fit more naturally, you'll have to override -pickerView:widthForComponent: to return a sane value for the appropriate component. This could involve hard coding a value or generating all the strings, sizing them each, and picking the largest one (plus some padding).

As noted previously, this always displays things in Month-Day-Year order. Making this dynamic to the current locale would be a little bit trickier. You'd have to get a @"d MMMM y" string localized to the current locale (hint: look at the class methods on NSDateFormatter for that), and then parse it to figure out what the order is.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded Moshe
Error: User Rate Limit Exceeded Moshe

Похожие вопросы