2012-04-04 13 views
15

Tôi đã nhận thấy rằng the UIDatePicker doesn't work with NSHebrewCalendar trong iOS 5.0 hoặc 5.1. Tôi đã quyết định thử và viết của riêng mình. Tôi đang bối rối như thế nào để cư trú dữ liệu và làm thế nào để duy trì các nhãn cho những ngày trong một cách sane và hiệu quả bộ nhớ.Làm cách nào để viết lại thành phần UIDatePicker?

Có bao nhiêu hàng trong mỗi thành phần? Khi nào các hàng được "tải lại" với các nhãn mới?

Tôi sẽ cung cấp ảnh này và tôi sẽ đăng khi tôi tìm ra, nhưng vui lòng đăng nếu bạn biết bất kỳ điều gì.

Trả lời

35

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:

custom hebrew date picker


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ì.

+10

Đ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 –

+0

Mã này thậm chí còn tuyệt vời hơn với ARC, btw. ;-) Jus 'nói. – Moshe

+3

@Dave DeLong là anh hùng của tôi. –

1

Tôi cho rằng bạn đang sử dụng UIPickerView?

UIPickerView hoạt động giống như một UITableView trong đó bạn chỉ định một lớp khác là dataSource/delegate và nó nhận dữ liệu của nó bằng cách gọi các phương thức trên lớp đó (nên tuân theo giao thức UIPickerViewDelegate/DataSource). Vì vậy, những gì bạn cần làm là tạo một lớp mới là một lớp con của UIPickerView, sau đó trong phương thức initWithFrame, đặt nó là nguồn dữ liệu/đại biểu của riêng nó và sau đó thực hiện các phương thức.

Nếu trước đây bạn chưa sử dụng UITableView, bạn nên tự làm quen với mẫu nguồn dữ liệu/ủy nhiệm vì có chút khó khăn để có được đầu xung quanh, nhưng một khi bạn hiểu nó rất dễ sử dụng.

Nếu trợ giúp, tôi đã viết UIPicker tùy chỉnh để chọn quốc gia. Này hoạt động trong khá nhiều giống như cách bạn sẽ muốn làm lớp học của bạn, ngoại trừ việc tôi đang sử dụng một loạt các tên đất nước thay vì ngày:

https://github.com/nicklockwood/CountryPicker

liên quan đến các câu hỏi bộ nhớ, các UIPickerView Với chỉ tải các nhãn được hiển thị trên màn hình và tái chế chúng khi chúng cuộn xuống dưới cùng và trở lại lên trên cùng, gọi lại cho đại biểu của bạn mỗi khi cần hiển thị hàng mới. Điều này là rất bộ nhớ hiệu quả vì sẽ chỉ có một vài nhãn trên màn hình tại một thời điểm.

+0

Có nhiều ngày có thể hơn các quốc gia có thể, tôi nghĩ đó là câu hỏi thực sự ở đây - bạn có bao nhiêu hàng trong bộ chọn ngày? – jrturton

+1

Bạn chia bộ chọn của mình thành ba cột, sau đó đặt ngày (31 hàng) tháng (12 hàng) và năm (một phạm vi phù hợp, thường là 1900 - hiện tại). UIPickerViewDelegate có một cuộc gọi lại numberofColumns cho phép bạn đặt số lượng cột bạn muốn. –

+0

Hoặc, nếu bạn muốn có một cột, chỉ cần đặt thuộc tính ngày/tối đa trên bộ chọn tùy chỉnh của bạn và sau đó tạo một hàng cho mỗi ngày giữa min và max. –