15

UIDatePickerが iOS 5.0 または 5.1の NSHebrewCalendar で機能しないことに気付きました。自分で書いてみることにしました。データを入力する方法と、日付のラベルを適切でメモリ効率の良い方法で維持する方法について、私は混乱しています。

各コンポーネントには実際に何行ありますか? 行が新しいラベルで「リロード」されるのはいつですか?

試してみて、わかり次第投稿しますが、何か知っていることがあれば投稿してください。

4

2 に答える 2

35

UIDatePickerまず、ヘブライ暦に関するバグを報告していただきありがとうございます。:)


編集iOS 6 がリリースされたのでUIDatePicker、ヘブライ暦で正しく動作するようになったため、以下のコードは不要になりました。ただし、後世に残しておきます。


あなたが発見したように、機能する日付ピッカーを作成することは難しい問題です。カバーする奇妙なエッジケースが無数にあるからです。ヘブライ暦はこの点で特に奇妙で、閏月( Adar I) がありますが、西洋文明のほとんどは 4 年に 1 日だけ余分な日を追加する暦に慣れています。

そうは言っても、最小限のヘブライ語の日付ピッカーを作成するのはそれほど複雑ではありませんUIDatePicker。それでは、簡単に説明しましょう。

@interface HPDatePicker : UIPickerView

@property (nonatomic, retain) NSDate *date;

- (void)setDate:(NSDate *)date animated:(BOOL)animated;

@end

サブクラス化してプロパティUIPickerViewのサポートを追加するだけです。、、、、および提供される他のすべての楽しいプロパティはdate無視します。これにより、作業がはるかに簡単になります。minimumDatemaximumDatelocalecalendartimeZoneUIDatePicker

実装は、クラス拡張から始めます。

@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オブジェクトを取り出して ivar としてキャッシュするので、常に調べている必要はありません。最後に、現在の日付で初期化します。

公開されたメソッドの実装は次のとおりです。

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

そして、ここから楽しいことが起こり始めます。

まず、与えられた日付を取得し、ヘブライ暦にそれを日付コンポーネント オブジェクトに分割するように依頼します。NSDate2012 年 4 月 4 日のグレゴリオ暦の日付に対応するを与えると、ヘブライ暦はNSDateComponents12 Nisan 5772 (2012 年 4 月 4 日と同じ日) に対応するオブジェクトを与えます。

この情報をもとに、ユニットごとにどの行を選択するかを考えて選択します。年号はシンプルです。単純に 1 を引きます (行は 0 から始まりますが、年は 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、他のカレンダー単位の繰り返しの性質を説明するために少し mod 操作を行います。

- (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 です」と言って「Adar I」を返してほしいとは言えません。したがって、気になる単位で必要な値を持つ人為的な日付を作成する必要があります。

年の場合、それは非常に簡単です。Tishri 1 でその年の日付コンポーネントを作成するだけでOKです。

何ヶ月もの間、その年がうるう年であることを確認する必要があります。そうすることで、現在の年が閏年であるかどうかに関係なく、月の名前が常に「Adar I」および「Adar II」になることを保証できます。

日については、任意の年を選びました。これは、すべての Tishri が 30 日であるためです (ヘブライ暦では 30 日を超える月はありません)。

適切な日付コンポーネント オブジェクトを構築したら、それを ivar ですばやく変換し、日付フォーマッタの書式文字列を設定NSDateして、hebrewCalendar関心のある単位の文字列のみを生成し、文字列を生成できます。

これをすべて正しく行ったと仮定すると、最終的には次のようになります。

カスタムヘブライ日付ピッカー


いくつかのメモ:

  • の実装を省略しました-pickerView:didSelectRow:inComponent:。選択した日付が変更されたことをどのように通知するかは、あなた次第です。

  • これは、無効な日付のグレー表示を処理しません。たとえば、現在選択されている年がうるう年でない場合は、「Adar I」をグレー表示にすることを検討してください。これには、メソッド-pickerView:viewForRow:inComponent:reusingView:の代わりに使用する必要があります。titleForRow:

  • UIDatePicker現在の日付が青色で強調表示されます。繰り返しますが、これを行うには、文字列ではなくカスタム ビューを返す必要があります。

  • 日付ピッカーは、UIPickerView. 青みがかったものだけUIDatePickersを入手してください。

  • ピッカー ビューのコンポーネントは、幅全体に広がります。より自然にフィットさせたい場合-pickerView:widthForComponent:は、適切なコンポーネントに適切な値を返すようにオーバーライドする必要があります。これには、値をハードコーディングするか、すべての文字列を生成し、それぞれのサイズを変更し、最大のものを選択する (およびいくつかのパディング) ことが含まれる場合があります。

  • 前述のように、これは常に月-日-年の順序で表示されます。これを現在のロケールに合わせて動的にするのは、少しトリッキーです。現在のロケールにローカライズされた文字列を取得し@"d MMMM y"(ヒント: そのためのクラス メソッドNSDateFormatterを参照)、それを解析して順序を把握する必要があります。

于 2012-04-05T03:03:47.777 に答える
1

UIPickerView を使用していると思いますか?

UIPickerView は、別のクラスを dataSource/delegate に指定するという点で UITableView のように機能し、そのクラスのメソッドを呼び出してデータを取得します (UIPickerViewDelegate/DataSource プロトコルに準拠する必要があります)。

したがって、UIPickerView のサブクラスである新しいクラスを作成し、initWithFrame メソッドでそれを独自のデータソース/デリゲートに設定してから、メソッドを実装する必要があります。

以前に UITableView を使用したことがない場合は、理解するのが少し難しいため、データソース/デリゲートのパターンに慣れておく必要がありますが、理解すれば非常に簡単に使用できます。

それが役立つ場合は、国を選択するためのカスタム UIPicker を作成しました。これは、日付の代わりに国名の配列を使用していることを除いて、クラスを行うのとほぼ同じ方法で機能します。

https://github.com/nicklockwood/CountryPicker

メモリの問題に関しては、UIPickerView は画面に表示されているラベルのみを読み込み、下からスクロールして上に戻るときにそれらをリサイクルし、新しい行を表示する必要があるたびにデリゲートを再度呼び出します。一度に画面に少数のラベルしか表示されないため、これは非常にメモリ効率が良いです。

于 2012-04-04T12:55:10.393 に答える