iOS 7 – Как отобразить выбор даты в виде таблицы?

В видеоролике WWDC 2013 Apple предлагает отображать сборщик на месте в виде таблицы в iOS 7. Как вставлять и анимировать представление между ячейками таблицы?

Как это, из приложения Apple Calendar:

Выбор даты на месте

С iOS7 Apple выпустила образец кода DateCell .

введите описание изображения здесь

Демонстрирует форматированное отображение объектов даты в ячейках таблицы и использование UIDatePicker для редактирования этих значений. В качестве делегата в этой таблице образец использует метод «didSelectRowAtIndexPath», чтобы открыть элемент управления UIDatePicker.

Для iOS 6.x и более ранних версий UIViewAnimation используется для сдвига UIDatePicker вверх и вниз по экрану. Для iOS 7.x UIDatePicker добавляется в линию в виде таблицы.

Метод действия UIDatePicker будет напрямую устанавливать свойство NSDate в пользовательской ячейке таблицы. Кроме того, в этом примере показано, как использовать class NSDateFormatter для достижения форматированного пользователем формата даты.

введите описание изображения здесь

Вы можете скачать образец кода здесь: DateCell .

Вы можете использовать ответ, который я ранее приводил ниже, или использовать этот новый class в Swift I, чтобы сделать эту задачу намного проще и чище: https://github.com/AaronBratcher/TableViewHelper


Я обнаружил, что код, предоставленный Apple, проблематичен несколькими способами:

  • У вас не может быть статический 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 от Apple и удалил файл раскадровки.

Если вы хотите один без раскадровки, посмотрите: https://github.com/ajaygautam/DateCellWithoutStoryboard

Одним из лучших руководств по этому поводу является iOS 7 in-line 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); } 

Чем переменная classа: 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); } 

Я сделал свой собственный пользовательский controller представлений, чтобы упростить процесс добавления встроенного сборщика inline в виде таблицы. Вы просто подclassифицируете его и следуете нескольким простым правилам и обрабатываете презентацию выбора даты.

Вы можете найти его здесь вместе с примером проекта, который демонстрирует, как его использовать: 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 без ячейки под ней.

Я просто присоединился к stackoverflow, поэтому, если это не в том месте или где-то еще, я приношу свои извинения.

Ответ от Aaron Bratcher работал, за исключением случаев, когда он используется с несколькими разделами. Анимация была немного изменчивой, и она не скользила по следующим разделам очень хорошо. Чтобы исправить это, я повторил следующий набор разделов и перевел строки на ту же сумму, что и высота выбора даты.

Я редактировал файл didSelectRowAtIndexPath для:

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

Добавляя к предыдущим ответам,

Я попробовал оба решения @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]; 

чтобы обновить строку, таким образом остальные части таблицы и ячейки не оживляют.

Надеюсь, это поможет кому-то.

Шани

Добавляя к предыдущим ответам и решение @Aaron Bratcher …

Я получал прерывистую анимацию с IOS 9, и на столе было время, чтобы загрузить, и достаточно, чтобы раздражать. Я сузил его, чтобы сборщики дат медленно загружались из раскадровки. Добавление сборщиков программно, а не в раскадровку улучшило производительность загрузки, а в качестве побочного продукта анимация стала более плавной.

Удалите сборщик дат из раскадровки и введите пустую ячейку, в которой вы задаете высоту, как в предыдущих ответах, а затем вызовите инициализацию в 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; } } 

Использование этого ответа без анимации работает правильно в 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() } } 

Вот еще один способ решить проблему без статических постоянных чисел. Все ячейки могут использоваться в статических и динамических представлениях таблиц. Этот метод использует одиночную ячейку для заголовка и выбора даты!

Кстати, у вас может быть так много выбора даты в вашем столе, как вы пожелаете!

Создайте подclass UITableViewCell :

Все ваши ячейки просмотра таблицы должны быть унаследованы от этого classа, и вы должны установить высоту ячейки вручную для каждой строки.

 // // CPTableViewCell.h // // Copyright (c) CodePigeon. All rights reserved. // @class CPTableViewCell; #define kUIAnimationDuration 0.33f @protocol CPTableViewCellDelegate  @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 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 

Создайте class CPDatePickerTableViewCell из нашего CPTableViewCell

 // // 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 ()  { 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 

В вашем представлении controller реализует эти два метода делегирования

 #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; } 

Пример настройки ограничений в построителе интерфейса

Конструктор интерфейсов

Кроме того, я написал собственные classы ячеек для UITextField и UITextView, где tableView: didSelectRowAtIndexPath: вызывается, когда выбрана ячейка!

CPTextFieldTableViewCell

 // // 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 ()  @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

Высота ячейки динамическая, и строка будет расти, когда текст будет перенесен на новую строку!

 // // 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 ()  { 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 

Самый простой способ использовать DateCell в версии Swift: используйте этот пример .

  1. Откройте этот пример и протестируйте его (сделайте Xcode для преобразования в Swift 2)
  2. Перетащите class « DateCellTableViewController.swift » в ваш проект.

  3. Откройте «Main.storyboard» и скопируйте объект ViewController « DateCell » и пройдите в его раскадровку.

  • Как узнать, когда UITableView завершил ReloadData?
  • Как я могу обнаружить двойной щелчок по определенной ячейке в UITableView?
  • Как прокрутить UITableView в определенную позицию
  • Запустить MBProgressHUD в другом streamе?
  • Кнопка удаления UITableViewCell закрывается
  • Продолжительность анимации строки UITableView и обратный вызов завершения
  • Использование iPhone - dequeueReusableCellWithIdentifier
  • Как предоставить пространство между двумя ячейками в виде таблицы?
  • Как остановить прокрутку UITextView при входе в нее
  • Выберите несколько строк в UITableview
  • Как resize таблицыHeaderView UITableView?
  • Давайте будем гением компьютера.