En primer lugar, gracias por enviar el fallo sobre UIDatePicker
y el calendario hebreo. :)
EDITAR Ahora que iOS 6 ha sido puesto en libertad, usted encontrará que UIDatePicker
ahora funciona correctamente con el calendario hebreo, por lo que el siguiente código innecesario. Sin embargo, lo dejaré para la posteridad.
Como usted ha descubierto, creando un selector de fecha de funcionamiento es un problema difícil, porque hay bajillions de los casos límite para cubrir extraños. El calendario hebreo es particularmente raro en este sentido, teniendo una intercalary month (Adar I), mientras que la mayor parte de la civilización occidental se utiliza para un calendario que sólo se agrega un día adicional alrededor de una vez cada 4 años.
Dicho esto, crear un selector de fecha hebreo mínimo no es demasiado complejo, suponiendo que esté dispuesto a renunciar a algunas de las sutilezas que ofrece UIDatePicker
. Así que vamos a hacer que sea sencillo:
@interface HPDatePicker : UIPickerView
@property (nonatomic, retain) NSDate *date;
- (void)setDate:(NSDate *)date animated:(BOOL)animated;
@end
Estamos simplemente yendo a la subclase UIPickerView
y agregar soporte para una propiedad date
.Vamos a ignorar minimumDate
, maximumDate
, locale
, calendar
, timeZone
, y todas las otras propiedades divertidas que ofrece UIDatePicker
. Esto hará que nuestro trabajo sea mucho más simple.
La aplicación va a comenzar con una extensión de clase:
@interface HPDatePicker() <UIPickerViewDelegate, UIPickerViewDataSource>
@end
simplemente para ocultar que el HPDatePicker
es su propia fuente de datos y delegado.
A continuación vamos a definir un par de constantes prácticos:
#define LARGE_NUMBER_OF_ROWS 10000
#define MONTH_COMPONENT 0
#define DAY_COMPONENT 1
#define YEAR_COMPONENT 2
Puede ver aquí que vamos a codificar el orden de las unidades de calendario. En otras palabras, este selector de fechas siempre mostrará las cosas como Mes-Día-Año, independientemente de las configuraciones personalizadas o locales que el usuario pueda tener. Por lo tanto, si está utilizando esto en una configuración regional donde el formato predeterminado sería "Día-Mes-Año", sería una pena. Para este simple ejemplo, esto será suficiente.
Ahora empezamos la implementación:
@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];
}
Estamos reemplazando el inicializador designado para hacer un poco de configuración para nosotros. Configuramos el delegado y el origen de datos para ser nosotros mismos, mostrar el indicador de selección y crear un objeto de calendario hebreo. También creamos un NSDateFormatter
y le decimos que debe formatear NSDates
de acuerdo con el calendario hebreo. También sacamos un par de objetos NSRange
y los almacenamos en la caché como ivars para que no tengamos que buscar constantemente. Finalmente, lo inicializamos con la fecha actual.
Estas son las implementaciones de los métodos expuestos:
- (void)setDate:(NSDate *)date {
[self setDate:date animated:NO];
}
-setDate:
simplemente reenvía a otro método
- (NSDate *)date {
NSDateComponents *c = [self selectedDateComponents];
return [hebrewCalendar dateFromComponents:c];
}
recuperar un NSDateComponents
representación de lo que esté seleccionado en ese momento, convertirlo en una NSDate
, y devolver eso.
- (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];
}
}
Y aquí es donde las cosas divertidas comienzan a suceder.
Primero, tomaremos la fecha que nos dieron y pediremos al calendario hebreo que lo descomponga en un objeto de componentes de fecha. Si le doy una NSDate
que corresponde a la fecha gregoriana, de 4 Abr 2012, a continuación, el calendario hebreo me va a dar a un objeto NSDateComponents
que corresponde al 12 Nisan 5772, que es el mismo día como el 4 2012 abr
En función de esta información, averiguo qué fila seleccionar en cada unidad y la selecciono. El caso del año es simple. Simplemente resta uno (las filas están basadas en cero, pero los años están basados en 1).
Durante meses, selecciono la columna del medio de las filas, descubro dónde comienza esa secuencia y le agrego el número de mes. Lo mismo con los días.
Las implementaciones de los métodos base <UIPickerViewDataSource>
son bastante triviales. Estamos mostrando 3 componentes, y cada uno tiene 10,000 filas.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 3;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return LARGE_NUMBER_OF_ROWS;
}
Obtener lo que se selecciona en este momento es bastante simple. Obtengo la fila seleccionada en cada componente y agrego 1 (en el caso de NSYearCalendarUnit
), o hago una pequeña operación de modificación para tener en cuenta la naturaleza repetitiva de las otras unidades de 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];
}
Por último, necesito algunos hilos para mostrar en la interfaz de usuario:
- (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
Aquí es donde las cosas son un poco más complicado. Desafortunadamente para nosotros, NSDateFormatter
solo puede formatear cosas cuando se le da un NSDate
real. No puedo simplemente decir "aquí hay un 6" y espero volver "Adar I". Por lo tanto, tengo que construir una fecha artificial que tenga el valor que quiero en la unidad que me importa.
En el caso de los años, eso es bastante simple. Solo crea los componentes de la fecha para ese año en Tishri 1, y estoy bien.
Durante meses, tengo que asegurarme de que el año sea un año bisiesto. Al hacerlo, puedo garantizar que los nombres de los meses siempre serán "Adar I" y "Adar II", independientemente de si el año actual es bisiesto o no.
Durante días, escogí un año arbitrario, porque cada Tishri tiene 30 días (y ningún mes en el calendario hebreo tiene más de 30 días).
Una vez que hemos construido la fecha apropiada objeto componentes, que puede convertirse rápidamente en un NSDate
con nuestro hebrewCalendar
Ivar, establecer la cadena de formato en el formateador fecha que sólo se produjeran listas para la unidad que nos importa, y generar una cuerda.
Suponiendo que usted ha hecho todo esto correctamente, usted va a terminar con esto:
Algunas notas:
He dejado de lado la puesta en práctica de -pickerView:didSelectRow:inComponent:
. Depende de usted averiguar cómo desea notificar que la fecha seleccionada ha cambiado.
Esto no controla el canto de las fechas no válidas. Por ejemplo, es posible que desee considerar encapsular "Adar I" si el año seleccionado actualmente no es un año bisiesto. Esto requeriría usar -pickerView:viewForRow:inComponent:reusingView:
en lugar del método titleForRow:
.
UIDatePicker
resaltará la fecha actual en azul. De nuevo, tendrías que devolver una vista personalizada en lugar de una cadena para hacer eso.
Su selector de fecha tendrá un bisel negruzco, porque es un UIPickerView
. Solo UIDatePickers
obtiene el azulado.
Los componentes de la vista del selector abarcarán todo su ancho. Si desea que las cosas se ajusten de forma más natural, deberá anular -pickerView:widthForComponent:
para devolver un valor razonable para el componente apropiado. Esto podría implicar la codificación difícil de un valor o la generación de todas las cadenas, el tamaño de cada uno, y la elección de la más grande (además de algunos relleno).
Como se indicó anteriormente, esto siempre muestra las cosas en orden de Mes-Día-Año. Hacer esto dinámico a la configuración regional actual sería un poco más complicado. Tendría que obtener una cadena @"d MMMM y"
localizada en la configuración regional actual (pista: mire los métodos de clase en NSDateFormatter
para eso), y luego analizarla para averiguar cuál es el orden.
Esta tiene que ser la mejor respuesta que he visto en desbordamiento de pila. +1 –
Este código se vuelve aún más impresionante con ARC, por cierto. ;-) Jus 'diciendo. – Moshe
@Dave DeLong es mi héroe. –