如何重写UIDatePicker组件? [英] How do I rewrite the UIDatePicker component?

查看:81
本文介绍了如何重写UIDatePicker组件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我注意到iOS中的 UIDatePicker不适用于NSHebrewCalendar 5.0或5.1。我决定尝试写自己的。我对如何填充数据以及如何以合理且内存有效的方式维护日期的标签感到困惑。

I've noticed that the UIDatePicker doesn't work with NSHebrewCalendar in iOS 5.0 or 5.1. I've decided to try and write my own. I'm confused as to how to populate the data and how to maintain the labels for the dates in a sane and memory efficient manner.

每个组件中实际有多少行?什么时候用新标签重新加载行?

How many rows are there actually in each component? When do the rows get "reloaded" with new labels?

我打算试一试,我会发现,但如果你知道的话,请发帖。

I'm going to give this a shot, and I'll post as I find out, but please post if you know anything.

推荐答案

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

First off, thanks for filing the bug about UIDatePicker and the Hebrew calendar. :)

编辑现在iOS 6已经发布,你会发现 UIDatePicker 现在可以正常使用希伯来语日历,使下面的代码变得不必要。但是,我会留给后人。

EDIT Now that iOS 6 has been released, you'll find that UIDatePicker now works correctly with the Hebrew calendar, making the code below unnecessary. However, I'll leave it for posterity.

正如您所发现的那样,创建一个有效的日期选择器是一件困难的事情。问题,因为有数百个奇怪的边缘案例需要覆盖。希伯来日历在这方面特别奇怪,有一个 intercalary month (Adar I),而大多数西方文明都习惯于每4年只增加一天的日历。

As you've discovered, creating a functioning date picker is a difficult problem, because there are bajillions of weird edge cases to cover. The Hebrew calendar is particularly weird in this regard, having an intercalary month (Adar I), whereas most of western civilization is used to a calendar that only adds an extra day about once every 4 years.

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

That being said, creating a minimal Hebrew date picker isn't too complex, assuming you're willing to forego some of the niceties that UIDatePicker offers. So let's make it simple:

@interface HPDatePicker : UIPickerView

@property (nonatomic, retain) NSDate *date;

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

@end

我们只是要继承 UIPickerView 并添加对 date 属性的支持。我们将忽略 minimumDate maximumDate locale 日历 timeZone ,以及所有其他有趣的属性 UIDatePicker 提供。这将使我们的工作更多更简单。

We're simply going to subclass UIPickerView and add support for a date property. We're going to ignore minimumDate, maximumDate, locale, calendar, timeZone, and all the other fun properties that UIDatePicker provides. This will make our job much simpler.

实施将从类扩展开始:

@interface HPDatePicker () <UIPickerViewDelegate, UIPickerViewDataSource>

@end

只需隐藏 HPDatePicker 是它自己的委托和数据源。

Simply to hide that the HPDatePicker is its own delegate and datasource.

接下来我们将定义一些方便的常量:

Next we'll define a couple of handy constants:

#define LARGE_NUMBER_OF_ROWS 10000

#define MONTH_COMPONENT 0
#define DAY_COMPONENT 1
#define YEAR_COMPONENT 2

您可以在此处看到我们将硬编码日历单位的顺序。换句话说,无论用户可能具有任何自定义设置或区域设置,此日期选择器将始终显示为月 - 日 - 年。因此,如果您在默认格式需要日 - 月 - 年的区域设置中使用此功能,那么太糟糕了。对于这个简单的例子,这就足够了。

You can see here that we're going to hard-code the order of the calendar units. In other words, this date picker will always display things as Month-Day-Year, regardless of any customizations or locale settings that the user may have. So if you're using this in a locale where the default format would want "Day-Month-Year", then too bad. For this simple example, this will suffice.

现在我们开始实现:

@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,因此我们不必经常查找。最后,我们用当前日期初始化它。

We're overriding the designated initializer to do some setup for us. We set the delegate and datasource to be ourself, show the selection indicator, and create a hebrew calendar object. We also create an NSDateFormatter and tell it that it should format NSDates according to the hebrew calendar. We also pull out a couple of NSRange objects and cache them as ivars so we don't have to constantly be looking things up. Finally, we initialize it with the current date.

以下是公开方法的实现:

Here are the implementations of the exposed methods:

- (void)setDate:(NSDate *)date {
    [self setDate:date animated:NO];
}

-setDate:只转发到另一种方法

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

检索 NSDateComponents 表示此刻选择的任何内容,将其转换为 NSDate ,然后返回。

Retrieve an NSDateComponents representing whatever is selected at the moment, turn it into an NSDate, and return that.

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

这就是有趣的东西开始发生的地方。

And this is where fun stuff starts happening.

首先,我们将采用我们给出的日期,并要求希伯来日历将其分解为日期组件对象。如果我给它一个 NSDate ,它对应于2012年4月4日的格里高利日期,那么希伯来日历将给我一个 NSDateComponents 对应12 Nisan 5772的对象,与2012年4月4日相同。

First, we'll take the date we were given and ask the hebrew calendar to break it up into a date components object. If I give it an NSDate that corresponds to the gregorian date of 4 Apr 2012, then the hebrew calendar is going to give me an NSDateComponents object that corresponds to 12 Nisan 5772, which is the same day as 4 Apr 2012.

根据这些信息,我找出要选择的行每个单位,并选择它。年份案例很简单。我简单地减去一个(行是从零开始的,但是年份是从1开始的。)

Based on this information, I figure out which row to select in each unit, and select it. The year case is simple. I simply subtract one (rows are zero-based, but years are 1-based).

几个月来,我选择行的中间列,找出它的位置序列开始,并将月份编号添加到它。与日期相同。

For months, I pick the middle of the rows column, figure out where that sequence starts, and add the month number to it. The same with the days.

基本< UIPickerViewDataSource> 方法的实现相当简单。我们正在显示3个组件,每个组件有10,000行。

The implementations of the base <UIPickerViewDataSource> methods are fairly trivial. We're displaying 3 components, and each one has 10,000 rows.

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 3;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    return LARGE_NUMBER_OF_ROWS;
}

获取当前所选的内容非常简单。我得到每个组件中的选定行,并添加1(在 NSYearCalendarUnit 的情况下),或执行一些mod操作以考虑其他日历单位的重复性质。

Getting what's selected at the current moment is fairly simple. I get the selected row in each component and either add 1 (in the case of NSYearCalendarUnit), or do a little mod operation to account for the repeating nature of the other calendar units.

- (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中显示一些字符串:

Finally, I need some strings to show in the 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。因此,我必须在我关心的单元中构建一个具有我想要的值的人工日期。

This is where things are a little bit more complicated. Unfortunately for us, NSDateFormatter can only format things when given an actual NSDate. I can't just say "here's a 6" and hope to get back "Adar I". Thus, I have to construct an artificial date that has the value I want in the unit I care about.

在多年的情况下,这非常简单。只需在Tishri 1上创建当年的日期组件,我就很好。

In the case of years, that's pretty simple. Just create a date components for that year on Tishri 1, and I'm good.

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

For months, I have to make sure that the year is a leap year. By doing so, I can guarantee that the month names will always be "Adar I" and "Adar II", regardless of whether the current year happens to be a leap year or not.

几天来,我挑选了一年,因为每个提市都有30天(希伯来日历中没有一个月超过30天)。

For days, I picked an arbitrary year, because every Tishri has 30 days (and no month in the Hebrew calendar has more than 30 days).

一次我们已经构建了相应的日期组件对象,我们可以使用我们的 hebrewCalendar ivar快速将其转换为 NSDate 日期格式化程序上的格式字符串只为我们关心的单位生成字符串,并生成一个字符串。

Once we've built the appropriate date components object, we can quickly turn it into an NSDate with our hebrewCalendar ivar, set the format string on the date formatter to only be producing strings for the unit we care about, and generate a string.

假设你已经完成了所有这些,那么你'最终得到这个:

Assuming you've done all this correctly, you'll end up with this:

一些注释:


  • 我省略了 -pickerView:didSelectRow:inComponent:的实现。您可以自行决定如何通知所选日期已更改。

  • I've left out the implementation of -pickerView:didSelectRow:inComponent:. It's up to you to figure out how you want to notify that the selected date changed.

这不会导致无效日期变灰。例如,如果当前选定的年份不是闰年,您可能需要考虑使Adar I变灰。这需要使用 -pickerView:viewForRow:inComponent:reusingView:而不是 titleForRow:方法。

This doesn't handle graying out invalid dates. For example, you might want to consider graying out "Adar I" if the currently selected year isn't a leap year. This would require using -pickerView:viewForRow:inComponent:reusingView: instead of the titleForRow: method.

UIDatePicker 将以蓝色突出显示当前日期。同样,您必须返回自定义视图而不是字符串才能执行此操作。

UIDatePicker will highlight the current date in blue. Again, you'd have to return a custom view instead of a string to do that.

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

Your date picker will have a blackish bezel, because it is a UIPickerView. Only UIDatePickers get the blueish one.

选择器视图的组件将跨越整个宽度。如果你想让事情更自然地适应,你必须覆盖 -pickerView:widthForComponent:来返回适当组件的合理值。这可能涉及硬编码值或生成所有字符串,每个字符串调整大小,并选择最大的字符串(加上一些填充)。

The components of the picker view will span its entire width. If you want things to fit more naturally, you'll have to override -pickerView:widthForComponent: to return a sane value for the appropriate component. This could involve hard coding a value or generating all the strings, sizing them each, and picking the largest one (plus some padding).

如前所述,这总是按月 - 日 - 年顺序显示。将此动态设置为当前区域设置会有点棘手。你必须得到一个 @MMMM y字符串本地化到当前语言环境(提示:查看 NSDateFormatter上的类方法为此,然后解析它以确定订单是什么。

As noted previously, this always displays things in Month-Day-Year order. Making this dynamic to the current locale would be a little bit trickier. You'd have to get a @"d MMMM y" string localized to the current locale (hint: look at the class methods on NSDateFormatter for that), and then parse it to figure out what the order is.

这篇关于如何重写UIDatePicker组件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆