2012-04-04 18 views
15

मैंने देखा है कि आईओएस 5.0 या 5.1 में the UIDatePicker doesn't work with NSHebrewCalendar। मैंने अपना खुद का प्रयास करने और लिखने का फैसला किया है। मैं इस बात से उलझन में हूं कि कैसे डेटा को पॉप्युलेट करना है और एक सैनी और मेमोरी कुशल तरीके से तिथियों के लिए लेबल को कैसे बनाए रखना है।मैं UIDatePicker घटक को फिर से लिखूं?

प्रत्येक घटक में वास्तव में कितनी पंक्तियां हैं? पंक्तियों को नए लेबल के साथ "पुनः लोड" कब मिलता है?

मैं इसे एक शॉट देने जा रहा हूं, और जैसा कि मुझे पता चला है, मैं पोस्ट करूंगा, लेकिन यदि आप कुछ जानते हैं तो कृपया पोस्ट करें।

उत्तर

35

पहले, बग के बारे में UIDatePicker और हिब्रू कैलेंडर दाखिल करने के लिए धन्यवाद। :)


संपादित अब जब कि IOS 6 जारी की गई है, तो आप उस UIDatePicker अब हिब्रू कैलेंडर के साथ सही ढंग से काम करता है, अनावश्यक नीचे कोड बनाने मिल जाएगा। हालांकि, मैं इसे जन्म के लिए छोड़ दूंगा। क्योंकि वहाँ अजीब बढ़त के मामलों की bajillions कवर करने के लिए कर रहे हैं


आप की खोज की है के रूप में, एक कामकाज दिनांक पिकर बनाने, एक कठिन समस्या है। हिब्रू कैलेंडर विशेष रूप से अजीब है, जिसमें intercalary 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 प्रदान करने वाले अन्य सभी मजेदार गुणों को अनदेखा करने जा रहे हैं। इससे हमारा काम अधिक आसान हो जाएगा।

कार्यान्वयन एक वर्ग विस्तार के साथ शुरू करने जा रहा है:

@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]; 
} 

अन्य विधि

- (NSDate *)date { 
    NSDateComponents *c = [self selectedDateComponents]; 
    return [hebrewCalendar dateFromComponents:c]; 
} 

को -setDate: बस आगे पर पुन: प्राप्त एक 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 उद्देश्य यह है कि निसान 5772 12 से मेल खाती है, जो 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]; 
} 

अंत में, मैं UI में दिखाने के लिए कुछ तार की जरूरत है:

- (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 नहीं" कह सकता हूं और "एडार आई" वापस पाने की उम्मीद करता हूं। इस प्रकार, मुझे एक कृत्रिम तारीख बनाना है जिसमें उस इकाई में मूल्य है जो मैं चाहता हूं।

वर्षों के मामले में, यह बहुत आसान है। बस उस वर्ष के लिए टिशरी 1 पर डेट घटक बनाएं, और मैं अच्छा हूं।

महीनों के लिए, मुझे यह सुनिश्चित करना होगा कि वर्ष एक लीप वर्ष है। ऐसा करके, मैं गारंटी दे सकता हूं कि महीने का नाम हमेशा "एडार आई" और "एडार II" होगा, भले ही वर्तमान वर्ष लीप वर्ष हो या नहीं।

दिनों के लिए, मैंने मनमाने ढंग से वर्ष चुना, क्योंकि हर तिशरी में 30 दिन होते हैं (और हिब्रू कैलेंडर में कोई भी महीना 30 दिनों से अधिक नहीं होता है)।

एक बार जब हम उपयुक्त तिथि घटकों वस्तु का निर्माण किया है, हम जल्दी से यह एक NSDate में हमारे hebrewCalendar इवर साथ, बारी केवल इकाई हम के बारे में परवाह के लिए तार का निर्माण किया जाना है तारीख फ़ॉर्मेटर पर प्रारूप स्ट्रिंग सेट है, और उत्पन्न कर सकते हैं एक स्ट्रिंग।

मान लें कि आप यह सब ठीक से किया है, तो आप इस के साथ खत्म हो जाएगा:

custom hebrew date picker


कुछ नोट:

  • मैं कार्यान्वयन बाहर छोड़ दिया है -pickerView:didSelectRow:inComponent: का। यह पता लगाने के लिए आप कैसे सूचित करना चाहते हैं कि चयनित तिथि बदल गई है।

  • यह अमान्य तिथियों को ग्रेइंग को संभाल नहीं करता है। उदाहरण के लिए, यदि आप वर्तमान में चयनित वर्ष लीप वर्ष नहीं है तो आप "एडार I" को भूरे रंग पर विचार करना चाहेंगे। titleForRow: विधि के बजाय इसे -pickerView:viewForRow:inComponent:reusingView: का उपयोग करने की आवश्यकता होगी।

  • UIDatePicker नीली रंग की वर्तमान तारीख को हाइलाइट करेगा। फिर, आपको ऐसा करने के लिए स्ट्रिंग के बजाय कस्टम दृश्य वापस करना होगा।

  • आपकी तिथि पिकर में ब्लैकिश बीज़ल होगा, क्योंकि यह UIPickerView है। केवल UIDatePickers नीली हो जाओ।

  • पिकर व्यू के घटक इसकी पूरी चौड़ाई बढ़ाएंगे। यदि आप चीजों को अधिक स्वाभाविक रूप से फिट करना चाहते हैं, तो आपको उचित घटक के लिए एक सौ मूल्य वापस करने के लिए -pickerView:widthForComponent: ओवरराइड करना होगा। इसमें मूल्य को हार्ड कोडिंग करना या सभी तारों को उत्पन्न करना, उनमें से प्रत्येक का आकार बनाना और सबसे बड़ा (प्लस कुछ पैडिंग) चुनना शामिल हो सकता है।

  • जैसा कि पहले उल्लेख किया गया है, यह हमेशा मासिक-वर्ष-वर्ष क्रम में चीजें प्रदर्शित करता है। इस गतिशील को वर्तमान लोकेल में बनाना थोड़ा सा ट्रिकियर होगा। आपको वर्तमान लोकेल में स्थानांतरित @"d MMMM y" स्ट्रिंग प्राप्त करना होगा (संकेत: उस के लिए NSDateFormatter पर कक्षा विधियों को देखें), और फिर ऑर्डर करने के लिए इसे पार्स करें।

+10

यह स्टैक ओवरफ़्लो पर मैंने कभी देखा है सबसे अच्छा जवाब होना चाहिए। +1 –

+0

यह कोड एआरसी, बीटीडब्ल्यू के साथ और भी बढ़िया हो जाता है। ;-) जुस 'कह रहा है। – Moshe

+3

@ डेव डीलॉन्ग मेरा नायक है। –

1

मुझे लगता है कि आप एक UIPickerView का उपयोग कर रहे हैं?

UIPickerView एक UITableView की तरह काम करता है जिसमें आप डेटा स्रोत/प्रतिनिधि होने के लिए एक और कक्षा निर्दिष्ट करते हैं, और इसे उस वर्ग पर विधियों को कॉल करके डेटा प्राप्त होता है (जो UIPickerViewDelegate/DataSource प्रोटोकॉल के अनुरूप होना चाहिए)।

तो आपको क्या करना चाहिए एक नई कक्षा बनाना जो यूआईपीकर व्यू का उप-वर्ग है, फिर initWithFrame विधि में, इसे अपना डेटासेट/प्रतिनिधि बनने के लिए सेट करें और फिर विधियों को लागू करें।

यदि आपने पहले UITableView का उपयोग नहीं किया है, तो आपको डेटासोर्स/प्रतिनिधि पैटर्न से परिचित होना चाहिए क्योंकि यह आपके सिर को पाने के लिए थोड़ा मुश्किल है, लेकिन एक बार जब आप इसे समझते हैं तो इसका उपयोग करना बहुत आसान है।

यदि यह मदद करता है, तो मैंने देशों का चयन करने के लिए एक कस्टम यूआईपीकर लिखा है। यह आपको अपने वर्ग के लिए करना चाहेंगे काफी उसी तरह से काम करता है, सिवाय इसके कि मैं बजाय देश के नाम की एक सरणी का उपयोग कर रहा तिथियों की:

https://github.com/nicklockwood/CountryPicker

स्मृति सवाल, UIPickerView के संबंध में केवल उन लेबलों को लोड करता है जो ऑनस्क्रीन दिखाई दे रहे हैं और उन्हें फिर से चलाते हैं क्योंकि वे नीचे और पीछे शीर्ष पर स्क्रॉल करते हैं, हर बार जब आपको एक नई पंक्ति प्रदर्शित करने की आवश्यकता होती है तो अपने प्रतिनिधि को कॉल करना। यह बहुत मेमोरी कुशल है क्योंकि एक समय में स्क्रीन पर केवल कुछ लेबल होंगे।

+0

संभावित देशों की तुलना में अधिक संभावित तिथियां हैं, मुझे लगता है कि यह असली सवाल है - आप अपनी तिथि पिकर में कितनी पंक्तियां शामिल करते हैं? – jrturton

+1

आप अपने पिकर को तीन कॉलम में विभाजित करते हैं, फिर दिन (31 पंक्तियां) महीने (12 पंक्तियां) और वर्षों (एक उपयुक्त सीमा, आमतौर पर 1 9 00 - वर्तमान) डाल दें। UIPickerViewDelegate में एक संख्या है कॉलम कॉलबैक जो आपको सेट करने देता है कि आप कितने कॉलम चाहते हैं। –

+0

वैकल्पिक रूप से, यदि आपके पास एक कॉलम होगा, तो बस अपने कस्टम पिकर पर एक न्यूनतम/अधिकतम दिनांक संपत्ति सेट करें और फिर प्रत्येक दिनांक के लिए न्यूनतम और अधिकतम के बीच एक पंक्ति उत्पन्न करें। –

संबंधित मुद्दे