我注意到在iOS 5.0或5.1中有the UIDatePicker doesn't work with NSHebrewCalendar。我決定嘗試寫我自己的。我很困惑如何填充數據,以及如何保持日期的標籤以一種健全和高效的方式保存。如何重寫UIDatePicker組件?
每個組件中實際存在多少行?什麼時候行使用新標籤「重新加載」?
我要給這個鏡頭,我會發布,因爲我發現,但請發佈,如果你知道任何事情。
我注意到在iOS 5.0或5.1中有the UIDatePicker doesn't work with NSHebrewCalendar。我決定嘗試寫我自己的。我很困惑如何填充數據,以及如何保持日期的標籤以一種健全和高效的方式保存。如何重寫UIDatePicker組件?
每個組件中實際存在多少行?什麼時候行使用新標籤「重新加載」?
我要給這個鏡頭,我會發布,因爲我發現,但請發佈,如果你知道任何事情。
首先,感謝提交有關UIDatePicker
和希伯來語日曆的錯誤。 :)
編輯現在的iOS 6已經發布,你會發現,UIDatePicker
現在希伯來日曆正常工作,使得下面不必要的代碼。但是,我會留給後代。
正如你已經發現,創建功能日期選擇器是一個棘手的問題,因爲有奇怪的邊緣情況bajillions覆蓋。希伯來語的日曆在這方面特別怪異,有intercalary month(Adar I),而大部分西方文明習慣於每四年增加一天的日曆。
這就是說,創建一個最小的希伯來日期選擇器並不是太複雜,假設你願意放棄一些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
你可以在這裏看到我們要硬編碼日曆單元的順序。換句話說,無論用戶可能擁有哪些自定義設置或區域設置,此日期選擇器都將始終將事件顯示爲「每月一日」。因此,如果您在默認格式需要「日 - 月 - 年」的語言環境中使用此功能,那麼太糟糕了。對於這個簡單的例子,這就足夠了。
現在我們開始實施:
@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];
}
}
這就是有趣的東西開始發生的地方。
首先,我們將獲取我們提供的日期,並要求希伯來語日曆將其分解爲日期組件對象。如果我給它對應於2012 4月4日的陽曆日子的NSDate
,然後希伯來日曆是要給我,對應於12尼桑5772的NSDateComponents
對象,它是同一天,2012年4月
基於這些信息,我計算出每個單元中要選擇的行,並選擇它。這一年的情況很簡單。我簡單地減去一個(行是基於零的,但是幾年是基於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」,並希望能夠回到「Adar I」。因此,我必須在我關心的單位中構建一個具有我想要的價值的人造日期。
在多年的情況下,這很簡單。只需在Tishri 1上創建一年的日期組件,而且我很好。
幾個月來,我必須確保今年是一個閏年。通過這樣做,無論本年度是否爲閏年,我都能保證月份名稱始終爲「Adar I」和「Adar II」。
幾天來,我選擇了一個任意一年,因爲每個提什裏都有30天(希伯來曆法中沒有一個月有超過30天)。
一旦我們已經建立了相應的日期組件對象,我們可以迅速地與我們的hebrewCalendar
伊娃把它變成一個NSDate
,在日期格式設置的格式字符串只能產生字符串,我們關心的單元,併產生一個字符串。
假設你已經正確地完成了這一切,你就這樣結束了:
一些注意事項:
我已經離開了實施-pickerView:didSelectRow:inComponent:
。由您決定如何通知所選日期已更改。
這不處理灰色無效日期。例如,如果當前選擇的年份不是閏年,則可能需要考慮將「Adar I」灰色化。這需要使用-pickerView:viewForRow:inComponent:reusingView:
而不是titleForRow:
方法。
UIDatePicker
將以藍色突出顯示當前日期。同樣,你必須返回一個自定義視圖,而不是一個字符串來做到這一點。
您的日期選擇器將有一個黑色的邊框,因爲它是一個UIPickerView
。只有UIDatePickers
得到藍色。
選取器視圖的組件將跨越其整個寬度。如果您希望事物更自然地適應,則必須重寫-pickerView:widthForComponent:
以爲適當的組件返回一個合理的值。這可能涉及對一個值進行硬編碼或者生成所有字符串,分別對它們進行大小調整,並挑選最大的一個(加上一些填充)。
如前所述,它總是以月 - 日 - 年順序顯示事物。使這種動態變爲當前的語言環境會有點棘手。你必須得到一個@"d MMMM y"
字符串本地化到當前的語言環境(提示:查看NSDateFormatter
上的類方法),然後解析它以找出順序是什麼。
我假設你使用的是UIPickerView?
UIPickerView的工作方式類似於UITableView,您可以指定另一個類作爲dataSource/delegate,並通過調用該類上的方法(應符合UIPickerViewDelegate/DataSource協議)獲取其數據。
所以你應該做的是創建一個新的類,它是UIPickerView的子類,然後在initWithFrame方法中,將它設置爲它自己的數據源/委託,然後實現這些方法。
如果你之前沒有使用過UITableView,那麼你應該熟悉數據源/委託模式,因爲要弄清楚你的頭腦有點棘手,但是一旦你理解了它就很容易使用。
如果有幫助,我寫了一個用於選擇國家的自定義UIPicker。這適用於幾乎你想要做你的類的方法相同,不同之處在於我使用國名的數組,而不是日期:
https://github.com/nicklockwood/CountryPicker
關於內存問題,UIPickerView只加載屏幕上可見的標籤,並在它們從底部滾動並返回到頂部時回收它們,每次需要顯示新行時再次調用委託。這非常有效,因爲一次只能在屏幕上顯示幾個標籤。
可能的日期比可能的國家多,我認爲這是真正的問題 - 您在日期選擇器中包含多少行? – jrturton 2012-04-04 13:33:39
您將您的選取器分成三列,然後放置幾天(31行)月份(12行)和年份(合適的範圍,通常爲1900 - 現在)。 UIPickerViewDelegate有一個numberofColumns回調,可以讓你設置你想要的列數。 – 2012-04-04 14:12:35
或者,如果您寧願有一列,只需在自定義選取器上設置最小/最大日期屬性,然後爲最小值和最大值之間的每個日期生成一行。 – 2012-04-04 14:13:17
這已經是我見過的堆棧溢出的最佳答案。 +1 – 2012-04-05 03:07:16
這個代碼用ARC來說更棒了,順便說一句。 ;-)朱斯說。 – Moshe 2012-04-05 03:38:17
@Dave DeLong是我的英雄。 – 2012-04-05 07:27:14