iOS 7 – Come visualizzare un selettore di date in atto in una visualizzazione tabella?

Nel video del WWDC 2013, Apple suggerisce di visualizzare il selettore in una vista tabella in iOS 7. Come inserire e animare una vista tra le celle di visualizzazione tabella?

In questo modo, dall’app del calendario Apple:

Selezione data sul posto

Con iOS7, Apple ha rilasciato il codice di esempio DateCell .

inserisci la descrizione dell'immagine qui

Dimostra la visualizzazione formattata degli oggetti data nelle celle della tabella e l’uso di UIDatePicker per modificare tali valori. Come delegato a questa tabella, l’esempio utilizza il metodo “didSelectRowAtIndexPath” per aprire il controllo UIDatePicker.

Per iOS 6.xe versioni precedenti, UIViewAnimation viene utilizzato per far scorrere UIDatePicker su schermo e verso il basso fuori dallo schermo. Per iOS 7.x, UIDatePicker viene aggiunto in linea alla vista tabella.

Il metodo di azione di UIDatePicker imposterà direttamente la proprietà NSDate della cella della tabella personalizzata. Inoltre, questo esempio mostra come utilizzare la class NSDateFormatter per ottenere l’aspetto formattato in base alla data della cella personalizzata.

inserisci la descrizione dell'immagine qui

Puoi scaricare il codice di esempio qui: DateCell .

Puoi utilizzare la risposta che avevo precedentemente indicato di seguito o utilizzare questa nuova class in Swift che ho creato per rendere questo compito molto più semplice e più pulito: https://github.com/AaronBratcher/TableViewHelper


Trovo che il codice fornito da Apple sia problematico in un paio di modi:

  • Non è ansible avere un tableView statico perché sta utilizzando il metodo tableView: cellForRowAtIndexPath
  • Il codice si arresta in modo anomalo se non ci sono righe aggiuntive al di sotto dell’ultima cella di selezione data

Per le tabelle di celle statiche, definisco la cella di selezione della data sotto la cella di visualizzazione della data e ho una bandiera che indica se la sto modificando. Se lo sono, restituisco l’altezza di una cella appropriata, altrimenti restituisco un’altezza di cella pari a zero.

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

Quando si fa clic sulla riga che mostra la data, cambio il flag e faccio l’animazione di aggiornamento per mostrare il selettore.

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

Se ho più selettori di data / ora nella stessa tabella, imposto i flag di conseguenza sul clic e ricarico le righe appropriate. Ho scoperto che posso mantenere il mio tavolo statico, usare molto meno codice ed è più facile capire cosa sta succedendo.

Utilizzando lo storyboard e una tabella statica sono riuscito a ottenere lo stesso risultato utilizzando il seguente codice. Questa è un’ottima soluzione perché se hai molte celle dalla forma strana o vuoi avere più celle che sono mostrate / nascoste in modo dinamico, questo codice funzionerà ancora.

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

Ho preso il datario DateCell da Apple e rimosso il file storyboard.

Se ne vuoi uno senza storyboard, dai un’occhiata a: https://github.com/ajaygautam/DateCellWithoutStoryboard

Uno dei migliori tutorial su questo è iOS 7 in linea UIDatePicker – Parte 2 . Fondamentalmente qui utilizzo celle di visualizzazione tabella statiche e implemento alcuni metodi aggiuntivi. Ho usato Xamarin e C # per questo:

Devi Clip Subviews .

Impostazione dell’altezza:

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

Di una variabile di class: private bool datePickerIsShowing = false;

Mostra selezione data:

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

Nascondi selezione data:

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

E chiamando queste funzioni:

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

Ho creato il mio controller di visualizzazione personalizzato per semplificare il processo di aggiunta di un selettore in linea inline in una vista tabella. La sottoclassi e segui alcune semplici regole e gestisce la presentazione del selettore di date.

Puoi trovarlo qui insieme a un progetto di esempio che dimostra come usarlo: https://github.com/ale84/ALEInlineDatePickerViewController

Ho trovato una risposta a un difetto nell’esempio di datecell di Apple in cui è necessario avere una riga sotto l’ultimo datecell o si ottiene un errore. Nel metodo CellForRowAtIndexPath, sostituire la riga ItemData con

 NSArray *itemsArray = [self.dataArray objectAtIndex:indexPath.section]; NSDictionary *itemData = nil; if(![indexPath isEqual:self.datePickerIndexPath]) itemData = [itemsArray objectAtIndex:modelRow]; 

Dopo aver sostituito il codice di esempio, ora posso visualizzare una cella datePicker senza avere una cella sottostante.

Mi sono appena iscritto a StackOverflow quindi se questo è nel posto sbagliato o in qualche altro posto, mi scuso.

La risposta di Aaron Bratcher ha funzionato tranne quando è stata utilizzata con più sezioni. Le animazioni erano un po ‘discontinue e non scorreva molto bene le prossime sezioni. Per risolvere questo problema ho iterato il prossimo set di sezioni e ho tradotto le righe con lo stesso importo dell’altezza del selettore della data.

Ho modificato il didSelectRowAtIndexPath su:

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

Aggiungendo alle risposte precedenti,

Ho provato entrambe le soluzioni @datinc e @Aaron Bratcher, entrambe hanno funzionato alla perfezione ma l’animazione non era così pulita in una tabella statica raggruppata.

Dopo aver giocato un po ‘ho capito questo codice che funziona pulito e ottimo per me –

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

Il principale cambiamento è usare –

  [super tableView:tableView heightForRowAtIndexPath:indexPath]; 

per aggiornare la riga, in questo modo il resto delle sezioni e delle celle della tabella non si animano.

Spero che aiuti qualcuno.

Shani

Aggiungendo alle risposte precedenti e alla soluzione @Aaron Bratcher …

Stavo ottenendo animazioni instabili da iOS 9, e il tavolo richiedeva un po ‘di tempo per essere caricato, e abbastanza per essere fastidioso. Lo ho ristretto ai raccoglitori di date che venivano caricati lentamente dallo storyboard. Aggiungendo i raccoglitori a livello di codice anziché nello storyboard è stata migliorata la prestazione di caricamento e, come sottoprodotto, l’animazione è più fluida.

Rimuovi il selettore di data dallo storyboard e disponi di una cella vuota, che imposta l’altezza come nelle risposte precedenti, quindi chiama un’inizializzazione su 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]; } 

Quindi attuare l’azione ad es

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

Questo carica la tabella molto più velocemente di prima. Rimuovi anche la linea di animazione da didSelectRowAtIndexPath mentre si anima senza problemi (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; } } 

L’utilizzo di questa risposta senza l’animazione funziona correttamente su iOS 8.1. L’ho convertito in Swift di seguito:

 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() } } 

Ecco un altro modo per risolvere il problema senza numeri costanti statici. Tutte le celle possono essere utilizzate in viste di tabelle statiche e dinamiche. Questo metodo utilizza una singola cella per il titolo e il selettore di date!

A proposito, puoi avere quanti selezionatori di date nel tuo tavolo vuoi!

Creare una sottoclass UITableViewCell :

Tutte le celle di visualizzazione tabella devono essere ereditate da questa class ed è necessario impostare manualmente l’altezza della cella per ogni riga.

 // // 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 

Crea una class CPDatePickerTableViewCell dal nostro 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 

Nel tuo controller vista implementare questi due metodi delegati

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

Esempio su come impostare i vincoli nel generatore di interfacce

Costruttore di interfaccia

Inoltre ho scritto classi di celle personalizzate per UITextField e UITextView dove tableView: didSelectRowAtIndexPath: viene chiamato quando cella è selezionata!

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

L’altezza della cella è dynamic e la riga crescerà quando il testo viene avvolto su una nuova riga!

 // // 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 

Il modo più semplice per usare DateCell nella versione Swift: usa questo esempio .

  1. Apri questo esempio e testalo (Crea Xcode per convertire in Swift 2)
  2. Trascina la class ” DateCellTableViewController.swift ” nel tuo progetto.

  3. Apri “Main.storyboard” e copia ” DateCellViewController Object e incollalo nello storyboard.