gpt4 book ai didi

objective-c - 如何重写 UIDatePicker 组件?

转载 作者:行者123 更新时间:2023-12-02 21:03:51 27 4
gpt4 key购买 nike

我注意到 the UIDatePicker doesn't work with NSHebrewCalendar在 iOS 5.0 或 5.1 中。我决定尝试写我自己的。我对如何填充数据以及如何以理智和内存高效的方式维护日期标签感到困惑。

每个组件中实际有多少行?何时用新标签“重新加载”行?

我打算试一试,我会在发现时发布,但如果您知道任何事情,请发布。

最佳答案

首先,感谢您提交关于 UIDatePicker 的错误。和希伯来历。 :)

编辑 现在iOS 6 已经发布,你会发现UIDatePicker现在可以与希伯来日历一起正常工作,因此不需要下面的代码。不过,我会把它留给后人。

正如您所发现的,创建一个正常运行的日期选择器是一个难题,因为有大量奇怪的边缘情况需要覆盖。希伯来历在这方面特别奇怪,它有一个 intercalary month (Adar I),而大多数西方文明习惯于每 4 年只增加一天的日历。

话虽如此,假设您愿意放弃 UIDatePicker 的一些细节,创建一个最小的希伯来语日期选择器并不太复杂。优惠。所以让我们简单点:

@interface HPDatePicker : UIPickerView

@property (nonatomic, retain) NSDate *date;

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

@end

我们只是要子类 UIPickerView并添加对 date 的支持属性(property)。我们将忽略 minimumDate , maximumDate , locale , calendar , timeZone ,以及所有其他有趣的属性 UIDatePicker提供。这将使我们的工作变得更加简单。

实现将从类扩展开始:
@interface HPDatePicker () <UIPickerViewDelegate, UIPickerViewDataSource>

@end

只是为了隐藏 HPDatePicker是它自己的委托(delegate)和数据源。

接下来我们将定义几个方便的常量:
#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];
}

我们正在覆盖指定的初始化程序来为我们做一些设置。我们将委托(delegate)和数据源设置为我们自己,显示选择指示器,并创建一个希伯来日历对象。我们还创建了一个 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];
}
}

这就是有趣的事情开始发生的地方。

首先,我们将获取给定的日期并要求希伯来日历将其分解为日期组件对象。如果我给它一个 NSDate对应于 2012 年 4 月 4 日的公历日期,那么希伯来历会给我一个 NSDateComponents对应于 12 Nisan 5772 的对象,即 2012 年 4 月 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 的情况下),或者做一些 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 上为那一年创建一个日期组件,我就很好。

几个月来,我必须确保这一年是闰年。通过这样做,我可以保证月份名称将始终为“Adar I”和“Adar II”,无论当前年份是否恰好是闰年。

对于几天,我选择了任意年份,因为每个提斯利月都有 30 天(希伯来历中没有一个月超过 30 天)。

一旦我们构建了适当的日期组件对象,我们就可以快速将其转换为 NSDate与我们的 hebrewCalendar ivar,将日期格式化程序上的格式字符串设置为只为我们关心的单元生成字符串,并生成一个字符串。

假设您已正确完成所有这些操作,您将得到以下结果:

custom hebrew date picker

一些注意事项:
  • 我忽略了 -pickerView:didSelectRow:inComponent: 的实现.由您决定如何通知所选日期已更改。
  • 这不能处理使无效日期变灰的问题。例如,如果当前选择的年份不是闰年,您可能需要考虑将“Adar I”灰显。这将需要使用 -pickerView:viewForRow:inComponent:reusingView:而不是 titleForRow:方法。
  • UIDatePicker将以蓝色突出显示当前日期。同样,您必须返回自定义 View 而不是字符串才能执行此操作。
  • 你的日期选择器会有一个黑色的边框,因为它是 UIPickerView .只有 UIDatePickers得到蓝色的。
  • 选择器 View 的组件将跨越其整个宽度。如果你想让事情更自然地适合,你必须覆盖 -pickerView:widthForComponent:为适当的组件返回一个合理的值。这可能涉及硬编码一个值或生成所有字符串,调整每个字符串的大小,并选择最大的一个(加上一些填充)。
  • 如前所述,这始终按月-日-年顺序显示内容。使这种动态适应当前的语言环境会有点棘手。你必须得到一个 @"d MMMM y"本地化为当前语言环境的字符串(提示:查看 NSDateFormatter 上的类方法),然后解析它以确定顺序是什么。
  • 关于objective-c - 如何重写 UIDatePicker 组件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10004778/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com