IOS7-如何在表格视图中显示日期选择器?

在 WWDC 2013视频中,苹果公司建议在 iOS7的表格视图中显示选择器。如何在表视图单元格之间插入视图并使其动画化?

像这样,从苹果的日历应用程序:

In-place date picker

87584 次浏览

在 iOS7中,苹果发布了样本代码 DateCell

enter image description here

演示表单元格中日期对象的格式化显示,以及使用 UIDatePicker 编辑这些值。 As a delegate to this table, the sample uses the method "didSelectRowAtIndexPath" to open the UIDatePicker control.

For iOS 6.x and earlier, UIViewAnimation is used for sliding the UIDatePicker up on-screen and down off-screen. For iOS 7.x, the UIDatePicker is added in-line to the table view.

The action method of the UIDatePicker will directly set the NSDate property of the custom table cell. In addition, this sample shows how to use NSDateFormatter class to achieve the custom cell's date-formatted appearance.

enter image description here

You can download the sample code here: DateCell.

您可以使用我以前在下面给出的答案,或者使用我在 Swift 中创建的这个新类来使这个任务更简单、更清晰: https://github.com/AaronBratcher/TableViewHelper


我发现苹果提供的代码在几个方面有问题:

  • 不能使用静态 tableView,因为它们使用 tableView: cellForRowAtIndexPath 方法
  • 如果在最后一个日期选择器单元格下面没有其他行,代码就会崩溃

对于静态单元格表,我在日期显示单元格下面定义了日期选择器单元格,并有一个标志来标识是否正在编辑它。如果是,则返回适当的单元格高度,否则返回零的单元格高度。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && indexPath.row == 2) { // this is my picker cell
if (editingStartTime) {
return 219;
} else {
return 0;
}
} else {
return self.tableView.rowHeight;
}
}

当单击显示日期的行时,我更改标志并执行更新动画以显示选择器。

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && indexPath.row == 1) { // this is my date cell above the picker cell
editingStartTime = !editingStartTime;
[UIView animateWithDuration:.4 animations:^{
[self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:2 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}];
}
}

如果在同一个表中有多个日期/时间选择器,则在单击并重新加载适当的行时相应地设置标志。我发现我可以保持我的静态表,使用更少的代码,并且更容易理解正在发生的事情。

通过使用情节串连图板和一个静态表,我能够使用以下代码实现相同的结果。这是一个很好的解决方案,因为如果您有许多形状奇怪的单元格,或者希望有多个动态显示/隐藏的单元格,这段代码仍然可以工作。

@interface StaticTableViewController: UITableViewController


@property (weak, nonatomic) IBOutlet UITableViewCell *dateTitleCell; // cell that will open the date picker. This is linked from the story board
@property (nonatomic, assign, getter = isDateOpen) BOOL dateOpen;


@end




@implementation StaticTableViewController


-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{


// This is the index path of the date picker cell in the static table
if (indexPath.section == 1 && indexPath.row == 1 && !self.isDateOpen){
return 0;
}
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}


-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
[tableView beginUpdates];
if (cell == self.dateTitleCell){
self.dateOpen = !self.isDateOpen;
}
[tableView reloadData];
[self.tableView endUpdates];
}

我从苹果获取了 DateCell 源代码,并删除了故事板文件。

如果你想要一个没有故事板的,看看: Https://github.com/ajaygautam/datecellwithoutstoryboard

我制作了自己的自定义视图控制器,以简化在表视图中内联添加内联选择器的过程。您只需要对它进行子类化,并遵循一些简单的规则,它就可以处理日期选择器的表示。

您可以在这里找到它,以及一个示例项目,演示如何使用它: Https://github.com/ale84/aleinlinedatepickerviewcontroller

我在苹果的日期单元格示例中找到了一个缺陷的答案,其中必须在最后一个日期单元格下面有一行,否则就会得到一个错误。在 CellForRowAtIndexPath 方法中,将 ItemData 行替换为

NSArray *itemsArray = [self.dataArray objectAtIndex:indexPath.section];
NSDictionary *itemData = nil;


if(![indexPath isEqual:self.datePickerIndexPath])
itemData = [itemsArray objectAtIndex:modelRow];

在替换了示例代码之后,我现在可以显示一个 datePicker 单元格,而不需要在其下面显示一个单元格。

I just joined stackoverflow so if this is in the wrong place or somewhere else I apologize.

亚伦 · 布拉彻的答案除了在多个部分中使用外,其他部分都是有效的。动画有点颠簸,下面的部分滑动得不是很好。为了解决这个问题,我迭代了接下来的一组部分,并将行的数量转换为与日期选择器的高度相同的数量。

我编辑了 did SelectRowAtIndexPath:

// Return Data to delegate: either way is fine, although passing back the object may be more efficient
// [_delegate imageSelected:currentRecord.image withTitle:currentRecord.title withCreator:currentRecord.creator];
// [_delegate animalSelected:currentRecord];
if (indexPath.section == 1 && indexPath.row == 0) { // this is my date cell above the picker cell
editingStartTime = !editingStartTime;
[UIView animateWithDuration:.4 animations:^{
int height = 0;
if (editingStartTime) {
height = 162;
}
UITableViewCell* temp = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]];
[temp setFrame:CGRectMake(temp.frame.origin.x, temp.frame.origin.y, temp.frame.size.width, height)];
for (int x = 2; x < [tableView numberOfSections]; x++) {
for (int y = 0; y < [tableView numberOfRowsInSection:x]; y++) {
UITableViewCell* temp = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:y inSection:x]];
int y_coord = temp.frame.origin.y-162;
if (editingStartTime) {
y_coord = temp.frame.origin.y+162;
}
[temp setFrame:CGRectMake(temp.frame.origin.x, y_coord, temp.frame.size.width, temp.frame.size.height)];
}
}
}completion:^(BOOL finished){
[self.tableView reloadData];
}];
}

关于这方面最好的教程之一是 IOS7嵌入式 UIDatePicker-第2部分。在这里,我基本上使用静态表视图单元格并实现一些其他方法。我使用了 Xamarin 和 C # :

你必须激活 Clip Subviews

设置高度:

public override float GetHeightForRow (UITableView tableView, NSIndexPath indexPath)
{
if (indexPath.Row == 4) {
return (datePickerIsShowing) ? 206f : 0.0f;
}


return base.GetHeightForRow(tableView,indexPath);
}

类变量: private bool datePickerIsShowing = false;

节目日期选择:

private void showDatePickerCell(){
datePickerIsShowing = true;
this.TableView.BeginUpdates ();
this.TableView.EndUpdates ();
this.datePicker.Hidden = false;
this.datePicker.Alpha = 0.0f;


UIView.Animate (0.25, animation:
() => {
this.datePicker.Alpha = 1.0f;
}
);
}

隐藏日期选择器:

private void hideDatePickerCell(){
datePickerIsShowing = false;
this.TableView.BeginUpdates ();
this.TableView.EndUpdates ();


UIView.Animate (0.25,
animation: () => {
this.datePicker.Alpha = 0.0f;
},
completion: () => {
this.datePicker.Hidden = true;
}
);
}

调用这个函数:

public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
if (indexPath.Row == 3) {
if (datePickerIsShowing) {
hideDatePickerCell ();
} else {
showDatePickerCell ();
}
}


this.TableView.DeselectRow (indexPath, true);
}

再加上之前的答案,

我尝试了@datinc 和@Aaron Bratcher 两种解决方案,它们都很好用,但是在一个分组的静态 tableView 中,动画就不那么干净了。

玩了一会儿之后,我得到了这个代码,工程干净和伟大的为我-

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && indexPath.row == 1)
{
if (self.isPickerOpened)
{
return 162;
}
else
{
return 0;
}
}
else
{
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0 && indexPath.row == 0) {
[tableView beginUpdates];
self.isPickerOpened = ! self.isPickerOpened;
[super tableView:tableView heightForRowAtIndexPath:indexPath];
[self.tableView endUpdates];
}
}

主要更改是使用-

        [super tableView:tableView heightForRowAtIndexPath:indexPath];

为了更新行,这样表的其余部分和单元格就不会有动画效果了。

希望能帮到别人。

莎妮

没有动画的 这个答案在 iOS 8.1中可以正常工作。我把它转换成了 Swift:

import UIKit


class TableViewController: UITableViewController {


var editingCell: Bool = false


@IBOutlet weak var myCell: UITableViewCell!


override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {


// Change the section and row to the title cell the user taps to reveal
// the cell below
if (indexPath.section == 0 && indexPath.row == 2 && !editingCell) {
return 0
} else {
return self.tableView.rowHeight
}
}


override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {


self.tableView.deselectRowAtIndexPath(indexPath, animated: false);


var cell = tableView.cellForRowAtIndexPath(indexPath)


self.tableView.beginUpdates()
if (cell == self.myCell) {
editingType = !editingType;
}
self.tableView.endUpdates()
}
}

这里有另一种方法来解决没有静态常数的问题。所有单元格都可以在静态和动态表视图中使用。此方法对标题和日期选择器使用单个单元格!

顺便说一下,你可以有许多日期采摘在您的表,因为你希望!

创建一个 UITableViewCell子类:

所有表视图单元格都必须是这个类中的 继承的,并且必须为每一行手动设置单元格高度。

//
//  CPTableViewCell.h
//
//  Copyright (c) CodePigeon. All rights reserved.
//


@class CPTableViewCell;


#define kUIAnimationDuration 0.33f


@protocol CPTableViewCellDelegate <NSObject>


@required
- (void)tableViewCellDidChangeValue:(CPTableViewCell *)cell;
@optional
- (void)tableViewCellDidBecomeFirstResponder:(CPTableViewCell *)cell;
- (void)tableViewCellResignedFirstResponder:(CPTableViewCell *)cell;
@end


@interface CPTableViewCell : UITableViewCell


@property (nonatomic, weak) IBOutlet UITableView *tableView;
@property (nonatomic, weak) IBOutlet CPTableViewCell *nextCell;
@property (nonatomic, weak) IBOutlet id<CPTableViewCellDelegate> delegate;


@property (nonatomic, copy) IBInspectable NSString *dataBindKey;
@property (nonatomic) IBInspectable CGFloat height;


@property (nonatomic, readonly) BOOL isFirstResponder;
@property (nonatomic) BOOL isEnabled;


- (void)commonInit;


- (id)value;
- (void)setValue:(id)value;


@end


//
//  CPTableViewCell.m
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPTableViewCell.h"


@interface CPTableViewCell ()
@end


@implementation CPTableViewCell


- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];


if (!self)
return nil;


[self commonInit];


return self;
}


- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];


if (!self)
return nil;


[self commonInit];


return self;
}


- (void)commonInit
{
_isFirstResponder = NO;
_isEnabled = YES;
}


- (BOOL)canBecomeFirstResponder
{
return _isEnabled;
}


- (BOOL)becomeFirstResponder
{
if ([_delegate respondsToSelector:@selector(tableViewCellDidBecomeFirstResponder:)])
[_delegate tableViewCellDidBecomeFirstResponder:self];


return _isFirstResponder = YES;
}


- (BOOL)resignFirstResponder
{
if (_isFirstResponder)
{
if ([_delegate respondsToSelector:@selector(tableViewCellResignedFirstResponder:)])
[_delegate tableViewCellResignedFirstResponder:self];


_isFirstResponder = NO;
}


return _isFirstResponder;
}


- (id)value
{
[self doesNotRecognizeSelector:_cmd];


return nil;
}


- (void)setValue:(id)value
{
[self doesNotRecognizeSelector:_cmd];
}


@end

从 CPTableViewCell 创建 PickerTableViewCell

//
//  CPDatePickerTableViewCell.h
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPTableViewCell.h"


@interface CPDatePickerTableViewCell : CPTableViewCell


@property (nonatomic, copy) IBInspectable NSString *dateFormat;


@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
@property (nonatomic, weak) IBOutlet UILabel *dateLabel;


@property (nonatomic, weak) IBOutlet UIDatePicker *datePicker;


@end


//
//  CPDatePickerTableViewCell.m
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPDatePickerTableViewCell.h"


#define kCPDatePickerTableViewCellPickerHeight 162.f


@interface CPDatePickerTableViewCell () <UITextFieldDelegate, UIPickerViewDelegate>
{
NSDateFormatter *_dateFormatter;
BOOL _isOpen;
}
@end


@implementation CPDatePickerTableViewCell


- (void)awakeFromNib
{
[super awakeFromNib];


_dateFormatter = [NSDateFormatter new];
[_dateFormatter setDateFormat:_dateFormat];


self.selectionStyle = UITableViewCellSelectionStyleNone;


_dateLabel.text = [_dateFormatter stringFromDate:_datePicker.date];
_datePicker.alpha = 0.f;
_isOpen = NO;
}


- (BOOL)becomeFirstResponder
{
if (_isOpen == NO)
{
self.height += kCPDatePickerTableViewCellPickerHeight;
}
else
{
self.height -= kCPDatePickerTableViewCellPickerHeight;
}


[UIView animateWithDuration:kUIAnimationDuration animations:^{
_datePicker.alpha = _isOpen ? 0.0f : 1.0f;
}];


[self.tableView beginUpdates];
[self.tableView endUpdates];


_isOpen = !_isOpen;


[self.tableView endEditing:YES];


return [super becomeFirstResponder];
}


- (BOOL)resignFirstResponder
{
if (_isOpen == YES)
{
self.height -= kCPDatePickerTableViewCellPickerHeight;


[UIView animateWithDuration:kUIAnimationDuration animations:^{
_datePicker.alpha = 0.0f;
}];


[self.tableView beginUpdates];
[self.tableView endUpdates];


_isOpen = NO;
}


return [super resignFirstResponder];
}


- (id)value
{
return _datePicker.date;
}


- (void)setValue:(NSDate *)value
{
_datePicker.date = value;
_dateLabel.text = [_dateFormatter stringFromDate:_datePicker.date];
}


- (IBAction)datePickerValueChanged:(UIDatePicker *)sender
{
[_dateLabel setText:[_dateFormatter stringFromDate:_datePicker.date]];


[self.delegate tableViewCellDidChangeValue:self];
}


@end

In your view controller implement these two delegate methods

#pragma mark - UITableViewDelegate methods


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CPTableViewCell *cell = (CPTableViewCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath];


return [cell height];
}


- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
CPTableViewCell *cell = (CPTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];


if ([cell canBecomeFirstResponder])
{
[cell becomeFirstResponder];
}


if (cell != _selectedCell)
{
[_selectedCell resignFirstResponder];
}


_selectedCell = cell;


return YES;
}

示例如何在 Interface Builder 中设置约束

Interface builder

此外,我还为 UITextFieldUITextView编写了自定义单元格类,在选择单元格时调用 tableView:didSelectRowAtIndexPath:

视图单元格

//
//  CPTextFieldTableViewCell.h
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPTableViewCell.h"


@interface CPTextFieldTableViewCell : CPTableViewCell


@property (nonatomic, weak) IBOutlet UITextField *inputTextField;


@end


//
//  CPTextFieldTableViewCell.m
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPTextFieldTableViewCell.h"


@interface CPTextFieldTableViewCell () <UITextFieldDelegate>
@end


@implementation CPTextFieldTableViewCell


- (void)awakeFromNib
{
[super awakeFromNib];


self.selectionStyle = UITableViewCellSelectionStyleNone;


_inputTextField.userInteractionEnabled = NO;
_inputTextField.delegate = self;
}


- (BOOL)becomeFirstResponder
{
_inputTextField.userInteractionEnabled = YES;


[_inputTextField becomeFirstResponder];


return [super becomeFirstResponder];
}


- (BOOL)resignFirstResponder
{
_inputTextField.userInteractionEnabled = NO;


return [super resignFirstResponder];
}


- (void)setIsEnabled:(BOOL)isEnabled
{
[super setIsEnabled:isEnabled];


_inputTextField.enabled = isEnabled;
}


- (id)value
{
return _inputTextField.text;
}


- (void)setValue:(NSString *)value
{
_inputTextField.text = value;
}


#pragma mark - UITextFieldDelegate methods


- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self.delegate tableViewCellDidChangeValue:self];
}


@end

视图单元格

单元格高度是动态的,当文本包装到新行时,行将增长!

//
//  CBTextViewTableViewCell.h
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPTableViewCell.h"


@interface CPTextViewTableViewCell : CPTableViewCell


@property (nonatomic, weak) IBOutlet UITextView *inputTextView;


@end


//
//  CBTextViewTableViewCell.m
//
//  Copyright (c) CodePigeon. All rights reserved.
//


#import "CPTextViewTableViewCell.h"


@interface CPTextViewTableViewCell () <UITextViewDelegate>
{
UITextView *_heightTextView;
}


@end


@implementation CPTextViewTableViewCell


@synthesize height = _height;


- (void)awakeFromNib
{
[super awakeFromNib];


self.selectionStyle = UITableViewCellSelectionStyleNone;


_inputTextView.userInteractionEnabled = NO;
_inputTextView.delegate = self;
_inputTextView.contentInset = UIEdgeInsetsZero;
_inputTextView.scrollEnabled = NO;
}


- (CGFloat)height
{
if (!_heightTextView)
{
CGRect frame = (CGRect) {
.origin = CGPointMake(0.f, 0.f),
.size = CGSizeMake(_inputTextView.textInputView.frame.size.width, 0.f)
};


_heightTextView = [[UITextView alloc] initWithFrame:frame];
_heightTextView.font = [UIFont systemFontOfSize:_inputTextView.font.pointSize];
_heightTextView.textColor = UIColor.whiteColor;
_heightTextView.contentInset = UIEdgeInsetsZero;
}


_heightTextView.text = _inputTextView.text;


CGSize size = [_heightTextView sizeThatFits:CGSizeMake(_inputTextView.textInputView.frame.size.width, FLT_MAX)];


return size.height > _height ? size.height + _inputTextView.font.pointSize : _height;
}


- (BOOL)becomeFirstResponder
{
_inputTextView.userInteractionEnabled = YES;


[_inputTextView becomeFirstResponder];


return [super becomeFirstResponder];
}


- (BOOL)resignFirstResponder
{
_inputTextView.userInteractionEnabled = NO;


return [super resignFirstResponder];
}


- (void)setIsEnabled:(BOOL)isEnabled
{
[super setIsEnabled:isEnabled];


_inputTextView.editable = isEnabled;
}


- (id)value
{
return _inputTextView.text;
}


- (void)setValue:(NSString *)value
{
_inputTextView.text = value;


[_inputTextView setNeedsLayout];
[_inputTextView layoutIfNeeded];
}


#pragma mark - UITextViewDelegate methods


- (void)textViewDidChange:(UITextView *)textView
{
[self.delegate tableViewCellDidChangeValue:self];


[self.tableView beginUpdates];
[self.tableView endUpdates];
}


@end

加上之前的答案和@Aaron Bratcher 的解决方案..。

自从 iOS9以来,我就开始收到断断续续的动画,而且这个表格需要一段时间才能载入,这已经够烦人的了。我把范围缩小到了日期选择者从故事板上加载的速度太慢。通过编程而不是在故事板中添加选择器提高了加载性能,作为副产品,动画更加平滑。

从情节串连图板中移除日期选择器,并且有一个空单元格,这个单元格的高度和前面的答案一样,然后在 viewDidLoad 上调用一个初始化:

- (void)initialiseDatePickers
{
self.isEditingStartTime = NO;
self.startTimePickerCell.clipsToBounds = YES;


UIDatePicker *startTimePicker = [[UIDatePicker alloc] init];
[startTimePicker addTarget:self action:@selector(startTimePickerChanged:) forControlEvents:UIControlEventValueChanged];
[self.startTimePickerCell addSubview:startTimePicker];
}

然后执行行动,例如。

- (IBAction)startTimePickerChanged:(id)sender
{
NSLog(@"start time picker changed");
}

这样加载表的速度比以前快得多。您还可以从 didSelectRowAtIndexPath中移除动画线,因为它在没有它(ymmv)的情况下可以平滑地动画。

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && indexPath.row == 1) { // this is my date cell above the picker cell
editingStartTime = !editingStartTime;
}
}

在 Swift 版本中使用 DateCell 最简单的方法是: 使用 这个例子

  1. 打开此示例并测试它(Make Xcode to Convert to Swift 2)
  2. 将“ DateCellTableViewController.swift”类拖到项目中。

  3. 打开“ Main.Storyboard”并复制“ 日期单元”ViewController 对象并在故事板中通过它。