如何重写的UIDatePicker成分?(How do I rewrite the UIDatePi

2019-06-23 09:53发布

我注意到, 在的UIDatePicker不NSHebrewCalendar工作中的iOS 5.0或5.1。 我决定尝试写我自己。 我很困惑,如何填充数据以及如何保持标签在一个健全的和记忆有效的方式的日期。

有多少行有实际的每个组件? 什么时候该行获得“重装”的新标签?

我要去给这一个镜头,我会后,我找到了,但请张贴如果你知道什么。

Answer 1:

首先,感谢您提交有关臭虫UIDatePicker和希伯来日历。 :)


编辑现在的iOS 6已经发布,你会发现UIDatePicker现在希伯来日历正常工作,使得下面不必要的代码。 不过,我会离开它为后人。


正如你已经发现,创建功能日期选择器是一个棘手的问题,因为有奇怪的边缘情况bajillions覆盖。 希伯来历法是在这方面特别奇怪,有一个闰月 (亚达I),而大多数西方文明的用于日历只增加大约每4年进行一次额外的一天。

话虽这么说,创建一个最小的希伯来日期选择器是不是太复杂了,假设你愿意放弃一些大乐事UIDatePicker提供。 因此,让我们把它简单:

@interface HPDatePicker : UIPickerView

@property (nonatomic, retain) NSDate *date;

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

@end

我们只是要继承UIPickerView ,并添加了支持date特性。 我们要忽略minimumDatemaximumDatelocalecalendartimeZone ,和所有的其他有趣特性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对象和缓存它们的实例变量,所以我们不必不断地寻找东西。 最后,我们以当前日期初始化。

以下是公开的方法的实现:

- (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尼散月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 ),或者做一个小的模操作以考虑其他日历单位的重复性质。

- (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”,希望找回“亚达我”。 因此,我必须构建具有我想在我关心的单位价值的人工日期。

在岁月的情况下,这是非常简单的。 只要创建当年就提市黎月1日期成分,我很好。

几个月来,我必须确保今年是闰年。 通过这样做,我可以保证一个月名字将永远是“亚达我”和“亚达II”,无论当前年份是否恰好是闰年。

一连几天,我拿起一个任意的一年,因为每提市黎月有30天(在希伯来日历没有月已超过30天)。

一旦我们已经建立了相应的日期组件对象,我们可以很快把它变成一个NSDate与我们的hebrewCalendar伊娃,上的日期格式设置格式字符串为我们所关心的单位只能产生字符串,并生成一个字符串。

假设你已经正确地完成了这一切,你就这样结束了:


一些注意事项:

  • 我已经离开了实施-pickerView:didSelectRow:inComponent: 它是由你来弄清楚要如何通知选定的日期改变。

  • 这不处理变灰无效的日期。 例如,你可能要考虑变灰“我亚达”如果当前选定的一年是不是闰年。 这就需要使用-pickerView:viewForRow:inComponent:reusingView:代替titleForRow:方法。

  • UIDatePicker将突出蓝色当前日期。 同样,你必须返回一个自定义视图,而不是字符串来做到这一点。

  • 您的日期选择器将有一个黑色的边框,因为它是一个UIPickerView 。 只有UIDatePickers得到偏蓝之一。

  • 选择器视图的部件将跨越其整个宽度。 如果你想要的东西,以适应更自然,你就必须重写-pickerView:widthForComponent:为适当的组件返回一个理智的价值。 这可能涉及硬编码值或产生的所有字符串,每个大小他们,采摘最大的一个(加上一些填充)。

  • 正如前面提到的,这始终显示在月 - 日 - 年顺序的东西。 使这个充满活力的当前区域将是一个有点棘手。 你必须得到@"d MMMM y"定位于当前的区域(提示:看看在类中的方法字符串NSDateFormatter为),然后分析它搞清楚的顺序是什么。



Answer 2:

我假设你正在使用一个UIPickerView?

UIPickerView就像在一个UITableView您指定的其他类是数据源/代表,并通过调用这个类的(应符合UIPickerViewDelegate /数据源协议)方法得到它的数据。

所以,你应该做的是创建一个新的类,它是UIPickerView的一个子类,然后在initWithFrame方法,将其设置为自己的数据源/委托,然后实现方法。

如果你以前没有用过一个UITableView,你应该熟悉数据源/委托模式,因为它是一个有点棘手左右让你的头,但一旦你理解它,它非常容易使用。

如果有帮助,我写了一个自定义的UIPicker选择国家。 这适用于几乎你想要做你的类的方法相同,不同之处在于我使用国名,而不是日期的数组:

https://github.com/nicklockwood/CountryPicker

关于内存问题,UIPickerView只加载是可见的标签屏幕上,并回收他们,因为他们滚动出底部和背部到顶部,再打电话给你的委托每次需要显示一个新行时间。 这是非常有效的内存,因为只有一次,会在屏幕上的几个标签。



文章来源: How do I rewrite the UIDatePicker component?