Prima di tutto, grazie per aver archiviato l'errore UIDatePicker
e il calendario ebraico. :)
EDIT Ora che iOS 6 è stato rilasciato, troverete che UIDatePicker
ora funziona correttamente con il calendario ebraico, rendendo il codice qui sotto non necessario. Tuttavia, lo lascerò ai posteri.
Come hai scoperto, la creazione di una data funzionamento Picker è un problema difficile, perché ci sono bajillions di casi limite strani per coprire. Il calendario ebraico è particolarmente strano a questo riguardo, avendo uno intercalary month (Adar I), mentre la maggior parte della civiltà occidentale è usata per un calendario che aggiunge solo un giorno in più circa una volta ogni 4 anni.
Detto questo, la creazione di un raccoglitore di date ebraico minimo non è troppo complessa, presupponendo che siate disposti a rinunciare ad alcune delle sottigliezze offerte da UIDatePicker
. Quindi cerchiamo di fare le cose semplici:
@interface HPDatePicker : UIPickerView
@property (nonatomic, retain) NSDate *date;
- (void)setDate:(NSDate *)date animated:(BOOL)animated;
@end
Stiamo semplicemente andando a creare una sottoclasse UIPickerView
e aggiungere il supporto per una proprietà date
.Ignoreremo minimumDate
, maximumDate
, locale
, calendar
, timeZone
e tutte le altre proprietà divertenti fornite da UIDatePicker
. Questo renderà il nostro lavoro molto più semplice.
L'implementazione sta per iniziare con un'estensione di classe:
@interface HPDatePicker() <UIPickerViewDelegate, UIPickerViewDataSource>
@end
semplicemente per nascondere che il HPDatePicker
è proprio delegato e origine dati.
successiva definiremo un paio di costanti a portata di mano:
#define LARGE_NUMBER_OF_ROWS 10000
#define MONTH_COMPONENT 0
#define DAY_COMPONENT 1
#define YEAR_COMPONENT 2
Si può vedere qui che stiamo andando a codificare l'ordine delle unità di calendario. In altre parole, questo selettore di date mostrerà sempre le cose come mese-giorno-anno, indipendentemente da eventuali personalizzazioni o impostazioni locali che l'utente potrebbe avere. Quindi, se stai usando questo in un locale in cui il formato predefinito vorrebbe "Day-Month-Year", allora troppo male. Per questo semplice esempio, questo sarà sufficiente.
Ora avviare l'attuazione:
@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];
}
Stiamo ignorando l'inizializzatore designato per fare qualche messa a punto per noi. Impostiamo il delegato e l'origine dati per essere noi stessi, mostriamo l'indicatore di selezione e creiamo un oggetto calendario ebraico. Creiamo anche un NSDateFormatter
e diciamo che dovrebbe formattare NSDates
secondo il calendario ebraico. Estraggiamo anche un paio di oggetti NSRange
e li memorizziamo nella cache come ivars, quindi non dobbiamo cercare costantemente le cose. Infine, inizializziamo con la data corrente.
Ecco le implementazioni dei metodi esposti:
- (void)setDate:(NSDate *)date {
[self setDate:date animated:NO];
}
-setDate:
appena avanti al altro metodo
- (NSDate *)date {
NSDateComponents *c = [self selectedDateComponents];
return [hebrewCalendar dateFromComponents:c];
}
recuperare un NSDateComponents
rappresenta indipendentemente selezionato al momento, trasformarlo in un NSDate
e restituiscilo.
- (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];
}
}
Ed è qui che iniziano le cose divertenti.
In primo luogo, prendiamo la data che ci è stata data e chiediamo al calendario ebraico di suddividerlo in un oggetto di componenti data. Se mi danno un NSDate
che corrisponde alla data gregoriano di 4 Apr 2012, poi il calendario ebraico sta per darmi un oggetto NSDateComponents
che corrisponde al 12 Nisan 5772, che è lo stesso giorno del 4 Apr 2012.
Sulla base di queste informazioni, ho individuato quale riga selezionare in ciascuna unità e selezionarla. Il caso dell'anno è semplice. Ho semplicemente sottratto uno (le righe sono basate su zero, ma gli anni sono basati su 1).
Per mesi, prendo il mezzo della colonna delle righe, capisco da dove inizia quella sequenza e aggiungo il numero del mese. Lo stesso con i giorni.
Le implementazioni dei metodi base <UIPickerViewDataSource>
sono abbastanza banali. Stiamo visualizzando 3 componenti e ognuno ha 10.000 righe.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 3;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return LARGE_NUMBER_OF_ROWS;
}
Ottenere ciò che viene selezionato al momento attuale è abbastanza semplice. Ottengo la riga selezionata in ciascun componente e ne aggiungo 1 (nel caso di NSYearCalendarUnit
) o eseguo una piccola operazione mod per tenere conto della natura ripetitiva delle altre unità del calendario.
- (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];
}
Infine, ho bisogno di alcune stringhe di mostrare nell'interfaccia utente:
- (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
Questo è dove le cose sono un po 'più complicato. Sfortunatamente per noi, NSDateFormatter
è in grado di formattare solo le cose quando viene dato un effettivo NSDate
. Non posso semplicemente dire "ecco un 6" e spero di tornare "Adar I". Quindi, devo costruire una data artificiale che abbia il valore che voglio nell'unità a cui tengo.
Nel caso di anni, è piuttosto semplice. Basta creare un componente per la data per quell'anno su Tishri 1 e sto bene.
Per mesi, devo assicurarmi che l'anno sia bisestile. Così facendo, posso garantire che i nomi dei mesi saranno sempre "Adar I" e "Adar II", indipendentemente dal fatto che l'anno in corso sia o meno un anno bisestile.
Per giorni, ho scelto un anno arbitrario, perché ogni Tishri ha 30 giorni (e nessun mese nel calendario ebraico ha più di 30 giorni).
Una volta che abbiamo costruito la data appropriata componenti oggetto, si può rapidamente trasformarsi in un NSDate
con il nostro hebrewCalendar
Ivar, impostare la stringa di formato della data formattatore solo essere la produzione di corde per l'unità ci sta a cuore, e generare una stringa.
Supponendo che hai fatto tutto questo correttamente, vi ritroverete con questo:
Alcune note:
ho lasciato fuori l'attuazione di -pickerView:didSelectRow:inComponent:
. Spetta a te capire come si desidera notificare che la data selezionata è cambiata.
Questo non gestisce le date non valide non valide. Ad esempio, potresti voler considerare l'annullamento di "Adar I" se l'anno selezionato non è un anno bisestile. Ciò richiederebbe l'utilizzo di -pickerView:viewForRow:inComponent:reusingView:
invece del metodo titleForRow:
.
UIDatePicker
evidenzierà la data corrente in blu. Ancora una volta, dovresti restituire una vista personalizzata anziché una stringa per farlo.
Il datario avrà una cornice nerastra, perché è un UIPickerView
. Solo lo UIDatePickers
ottiene quello bluastro.
I componenti della vista di selezione copriranno l'intera larghezza. Se si desidera che le cose si adattino in modo più naturale, sarà necessario eseguire l'override di -pickerView:widthForComponent:
per restituire un valore corretto per il componente appropriato. Ciò potrebbe comportare la codifica di un valore o la generazione di tutte le stringhe, il ridimensionamento di ciascuna di esse e la selezione della più grande (più una spaziatura interna).
Come indicato in precedenza, questo visualizza sempre le cose nell'ordine Mese-Giorno-Anno. Rendere questa dinamica al locale corrente sarebbe un po 'più complicato. Dovresti ottenere una stringa @"d MMMM y"
localizzata nelle impostazioni internazionali correnti (suggerimento: guarda i metodi di classe su NSDateFormatter
per quello), quindi analizzala per capire qual è l'ordine.
Questa è la migliore risposta che abbia mai visto sullo stack overflow. +1 –
Questo codice diventa ancora più fantastico con ARC, btw. ;-) Jus 'dicendo. – Moshe
@Dave DeLong è il mio eroe. –