Trước hết, cảm ơn bạn đã gửi lỗi về số UIDatePicker
và lịch Do Thái. :)
EDIT Bây giờ iOS 6 đã được phát hành, bạn sẽ thấy rằng UIDatePicker
hiện đang làm việc một cách chính xác với lịch Hebrew, làm cho mã bên dưới không cần thiết. Tuy nhiên, tôi sẽ để nó cho hậu thế.
Như bạn đã khám phá, tạo bộ chọn ngày hoạt động là một vấn đề khó khăn, bởi vì có rất nhiều trường hợp cạnh kỳ lạ để che. Lịch Do Thái đặc biệt kỳ lạ về vấn đề này, có một số intercalary month (Adar I), trong khi hầu hết nền văn minh phương Tây được sử dụng cho lịch chỉ thêm một ngày thêm khoảng 4 năm một lần.
Điều đó đang được nói, việc tạo một bộ chọn ngày tiếng Do Thái tối thiểu không quá phức tạp, giả sử bạn sẵn sàng bỏ qua một số ưu đãi mà UIDatePicker
cung cấp. Vì vậy, hãy làm cho nó đơn giản:
@interface HPDatePicker : UIPickerView
@property (nonatomic, retain) NSDate *date;
- (void)setDate:(NSDate *)date animated:(BOOL)animated;
@end
Chúng tôi chỉ đơn giản là sẽ phải phân lớp UIPickerView
và thêm hỗ trợ cho một tài sản date
.Chúng tôi sẽ bỏ qua minimumDate
, maximumDate
, locale
, calendar
, timeZone
và tất cả các thuộc tính thú vị khác mà UIDatePicker
cung cấp. Điều này sẽ làm cho công việc của chúng tôi nhiều hơn đơn giản hơn.
Việc thực hiện sẽ bắt đầu với một phần mở rộng lớp:
@interface HPDatePicker() <UIPickerViewDelegate, UIPickerViewDataSource>
@end
Đơn giản chỉ cần để che giấu rằng HPDatePicker
là đại biểu và nguồn dữ liệu riêng của mình.
Tiếp theo chúng ta sẽ xác định một vài hằng ích:
#define LARGE_NUMBER_OF_ROWS 10000
#define MONTH_COMPONENT 0
#define DAY_COMPONENT 1
#define YEAR_COMPONENT 2
Bạn có thể thấy ở đây rằng chúng ta sẽ mã hóa cứng thứ tự của các đơn vị lịch. Nói cách khác, bộ chọn ngày này sẽ luôn hiển thị mọi thứ dưới dạng Tháng-Ngày-Năm, bất kể bất kỳ tùy chỉnh hoặc cài đặt ngôn ngữ nào mà người dùng có thể có. Vì vậy, nếu bạn đang sử dụng điều này trong một miền địa phương nơi định dạng mặc định sẽ muốn "Ngày-Tháng-Năm", sau đó quá xấu. Đối với ví dụ đơn giản này, điều này là đủ.
Bây giờ chúng ta bắt đầu thực hiệ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];
}
Chúng tôi đang trọng initializer được chỉ định làm một số thiết lập cho chúng ta. Chúng tôi thiết lập các đại biểu và nguồn dữ liệu để được ourself, hiển thị các chỉ số lựa chọn, và tạo ra một đối tượng lịch hebrew. Chúng tôi cũng tạo ra một NSDateFormatter
và nói với nó rằng nó phải định dạng NSDates
theo lịch tiếng do thái. Chúng tôi cũng kéo ra một vài đối tượng NSRange
và lưu trữ chúng dưới dạng hình chữ nhật để chúng tôi không phải liên tục tìm kiếm mọi thứ. Cuối cùng, chúng tôi khởi tạo nó với ngày hiện tại.
Dưới đây là việc triển khai các phương pháp tiếp xúc:
- (void)setDate:(NSDate *)date {
[self setDate:date animated:NO];
}
-setDate:
chỉ chuyển tiếp sang phương pháp khác
- (NSDate *)date {
NSDateComponents *c = [self selectedDateComponents];
return [hebrewCalendar dateFromComponents:c];
}
Lấy một NSDateComponents
đại diện cho bất cứ điều gì được chọn vào lúc này, biến nó thành một NSDate
và trả lại điều đó.
- (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];
}
}
Và đây là điều thú vị bắt đầu xảy ra.
Trước tiên, chúng tôi sẽ lấy ngày chúng tôi đã đưa ra và yêu cầu lịch hebrew để chia nhỏ thành đối tượng thành phần ngày. Nếu tôi cho nó một số NSDate
tương ứng với ngày gregorian ngày 4 tháng 4 năm 2012, thì lịch hebrew sẽ cho tôi một đối tượng NSDateComponents
tương ứng với 12 Nisan 5772, cùng ngày với 4 tháng 4 năm 2012.
Dựa trên thông tin này, tôi tìm ra hàng để chọn trong mỗi đơn vị, và chọn nó. Trường hợp năm là đơn giản. Tôi chỉ đơn giản là trừ một (hàng là không dựa trên, nhưng năm là 1-based).
Trong nhiều tháng, tôi chọn giữa cột hàng, tìm ra nơi chuỗi đó bắt đầu và thêm số tháng vào đó. Cùng với những ngày.
Việc triển khai các phương thức cơ bản <UIPickerViewDataSource>
là khá tầm thường. Chúng tôi đang hiển thị 3 thành phần và mỗi thành phần có 10.000 hàng.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 3;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return LARGE_NUMBER_OF_ROWS;
}
Việc chọn những gì được chọn tại thời điểm hiện tại khá đơn giản. Tôi nhận được hàng đã chọn trong mỗi thành phần và thêm 1 (trong trường hợp NSYearCalendarUnit
) hoặc thực hiện một thao tác mod nhỏ để tính đến tính chất lặp lại của các đơn vị lịch khác.
- (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];
}
Cuối cùng, tôi cần một số chuỗi để hiển thị trong giao diện người dùng:
- (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
Đây là nơi mà mọi thứ phức tạp hơn một chút. Thật không may cho chúng tôi, NSDateFormatter
chỉ có thể định dạng mọi thứ khi được cung cấp NSDate
thực tế. Tôi không thể nói "đây là 6" và hy vọng sẽ quay lại "Adar I". Vì vậy, tôi phải xây dựng một ngày nhân tạo có giá trị tôi muốn trong đơn vị tôi quan tâm.
Trong trường hợp nhiều năm, điều đó khá đơn giản. Chỉ cần tạo một thành phần ngày cho năm đó trên Tishri 1, và tôi tốt.
Trong nhiều tháng, tôi phải đảm bảo rằng năm đó là năm nhuận. Bằng cách đó, tôi có thể đảm bảo rằng tên tháng sẽ luôn là "Adar I" và "Adar II", bất kể năm nay có phải là năm nhuận hay không.
Trong nhiều ngày, tôi đã chọn một năm tùy ý, vì mỗi Tishri có 30 ngày (và không có tháng nào trong lịch Do Thái có hơn 30 ngày).
Khi chúng tôi đã xây dựng đối tượng thành phần ngày thích hợp, chúng tôi có thể nhanh chóng biến nó thành một NSDate
với mã vạch hebrewCalendar
ivar của chúng tôi, đặt chuỗi định dạng trên trình định dạng ngày để chỉ tạo chuỗi cho đơn vị chúng tôi quan tâm và tạo một chuỗi.
Giả sử bạn đã làm tất cả điều này một cách chính xác, bạn sẽ kết thúc với điều này:

Một số lưu ý:
tôi đã rời ra thi của -pickerView:didSelectRow:inComponent:
. Đó là vào bạn để tìm ra cách bạn muốn thông báo rằng ngày đã chọn thay đổi.
Điều này không xử lý các ngày không có màu xám. Ví dụ: bạn có thể muốn xem xét chuyển sang màu xám "Adar I" nếu năm hiện được chọn không phải là năm nhuận. Điều này sẽ yêu cầu sử dụng -pickerView:viewForRow:inComponent:reusingView:
thay vì phương pháp titleForRow:
.
UIDatePicker
sẽ đánh dấu ngày hiện tại bằng màu xanh dương. Một lần nữa, bạn sẽ phải trả về một khung nhìn tùy chỉnh thay vì một chuỗi để làm điều đó.
Bộ chọn ngày của bạn sẽ có một bezel đen, vì nó là UIPickerView
. Chỉ UIDatePickers
lấy màu xanh lam.
Các thành phần của chế độ xem bộ chọn sẽ trải rộng toàn bộ chiều rộng. Nếu bạn muốn mọi thứ phù hợp với tự nhiên hơn, bạn sẽ phải ghi đè -pickerView:widthForComponent:
để trả lại giá trị lành mạnh cho thành phần thích hợp. Điều này có thể liên quan đến việc mã hóa cứng một giá trị hoặc tạo tất cả các chuỗi, định cỡ chúng, và chọn một chuỗi lớn nhất (cộng với một số phần đệm).
Như đã lưu ý trước đây, điều này luôn hiển thị mọi thứ theo thứ tự Ngày-Tháng-Năm. Làm cho động này trở thành ngôn ngữ hiện tại sẽ phức tạp hơn một chút. Bạn sẽ phải nhận được một chuỗi @"d MMMM y"
được bản địa hóa sang ngôn ngữ hiện tại (gợi ý: xem xét các phương thức lớp trên NSDateFormatter
cho điều đó), và sau đó phân tích nó để tìm ra thứ tự là gì.
Điều này đã có được câu trả lời tốt nhất mà tôi từng thấy trên tràn ngăn xếp. +1 –
Mã này thậm chí còn tuyệt vời hơn với ARC, btw. ;-) Jus 'nói. – Moshe
@Dave DeLong là anh hùng của tôi. –