iOS 7 - Jak wyświetlić selektor dat na miejscu w widoku tabeli?

121

W wideo WWDC 2013 Apple sugeruje wyświetlanie selektora na miejscu w widoku tabeli w iOS 7. Jak wstawiać i animować widok między komórkami widoku tabeli?

W ten sposób z aplikacji kalendarza Apple:

Lokalny selektor dat

XY
źródło
Który film WWDC to jest? Próbuję zrozumieć, dlaczego jest to specyficzne dla iOS7 i czy istnieje powód, dla którego nie można tego zrobić we wcześniejszych wersjach.
Clafou,
4
Znalazłem to, to „Co nowego w projektowaniu interfejsu użytkownika iOS” (dzięki ASCIIwwdc). Powodem jest to, że stary styl nie wyglądał dobrze w linii, ale nowy płaski wygląd tak.
Clafou

Odpowiedzi:

102

W systemie iOS7 firma Apple udostępniła przykładowy kod DateCell.

wprowadź opis obrazu tutaj

Pokazuje sformatowane wyświetlanie obiektów daty w komórkach tabeli i użycie UIDatePicker do edycji tych wartości. Jako delegat do tej tabeli przykład używa metody „didSelectRowAtIndexPath” do otwierania formantu UIDatePicker.

W przypadku systemu iOS 6.xi starszych wersji UIViewAnimation służy do przesuwania UIDatePicker w górę na ekranie i w dół poza ekranem. W systemie iOS 7.x UIDatePicker jest dodawany w wierszu do widoku tabeli.

Metoda akcji UIDatePicker bezpośrednio ustawi właściwość NSDate niestandardowej komórki tabeli. Ponadto ten przykład pokazuje, jak używać klasy NSDateFormatter, aby uzyskać wygląd sformatowanej daty w komórce niestandardowej.

wprowadź opis obrazu tutaj

Możesz pobrać przykładowy kod tutaj: DateCell .

Nitin Gohel
źródło
Kod Apple ma problemy. Zobacz moją odpowiedź poniżej, jeśli chcesz zachować statyczny
widok tabeli
1
@AaronBratcher Właśnie przedstawiam Datecell w mojej odpowiedzi i nigdy nie oczekujesz, że otrzymasz dokładnie taki kod, jaki chcesz. Musisz modyfikować i zmieniać zgodnie z wymaganiami. Twoje rozwiązanie jest również dobre, ale pamiętaj, że nigdy nie uzyskasz dokładnie tego, co z innego wymaganego kodu.
Nitin Gohel
3
najstraszniejszy przykładowy kod.
Przydałyby się
2
Wow, kod Apple jest… cóż, powiedzmy, że skupiamy się na efekcie widoku tabeli. Mam nadzieję, że nikt nie zainspiruje się ich kodem w tym przykładowym projekcie: s
Daniel
84

Możesz skorzystać z odpowiedzi, którą wcześniej podałem poniżej, lub użyć tej nowej klasy w Swift, którą wykonałem, aby to zadanie było o wiele prostsze i czystsze: https://github.com/AaronBratcher/TableViewHelper


Uważam, że kod dostarczony przez Apple jest problematyczny z kilku powodów:

  • Nie możesz mieć statycznego tableView, ponieważ używają metody tableView: cellForRowAtIndexPath
  • Kod ulega awarii, jeśli nie masz dodatkowych wierszy pod ostatnią komórką selektora dat

W przypadku tabel statycznych komórek definiuję komórkę selektora dat poniżej komórki wyświetlającej datę i mam flagę określającą, czy ją edytuję. Jeśli tak, zwracam odpowiednią wysokość komórki, w przeciwnym razie zwracam wysokość komórki równą 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;
    }
}

Po kliknięciu wiersza z datą zmieniam flagę i wykonuję animację aktualizacji, aby pokazać selektor.

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

Jeśli mam wiele selektorów daty / czasu w tej samej tabeli, ustawiam flagi odpowiednio po kliknięciu i ponownie ładuję odpowiednie wiersze. Odkryłem, że mogę zachować moją statyczną tabelę, używać o wiele mniej kodu i łatwiej jest zrozumieć, co się dzieje.

Aaron Bratcher
źródło
To zadziałało dla mnie, z wyjątkiem tego, że wydaje się, że jest błąd w UITableView. W mojej metodzie wysokość wiersza była zwracana prawidłowo, ale w rzeczywistości przełączała się na przeciwną wysokość, niż chciałem. Jeśli dwukrotnie przeładowałem wiersze, zadziałało. (Nie korzystałem z tableView reloadData, ponieważ nie chciałem przeładowywać całości). Również blok animacji nie był konieczny, wydaje się, że animuje się sam.
Chris Garrett,
Czy ktoś użył tego przykładu do utworzenia selektora tekstu? Jeśli tak to jak? Dzięki
TheGeezer,
1
Chcesz odsłonić wiersz, aby użytkownik mógł wprowadzić tekst zamiast wybierać datę? Jeśli tak, kod jest taki sam. Tylko zawartość ujawnionego wiersza jest inna.
Aaron Bratcher
22
Mamy błąd w iOS7.1, który pokazuje obiekty poza granicami komórki - takie jak ten z wysokością 0 i selektorem w środku. Rozwiązaniem jest zdobycie gniazdka dla komórki i ustawienie cell.clipsToBounds = YES;
EmilDo
1
@Dunken - rozwiązanie jest opisane w głosowanym powyżej komentarzu: stackoverflow.com/questions/18973573/… Mimo że wiersz ma wysokość zero, zawartość nie jest domyślnie przycięta, więc można ją zobaczyć pod następny wiersz.
Chris Nolet
18

Korzystając ze scenorysu i statycznej tabeli, udało mi się osiągnąć ten sam wynik, używając poniższego kodu. To świetne rozwiązanie, ponieważ jeśli masz wiele komórek o dziwnym kształcie lub chcesz mieć wiele komórek, które są dynamicznie pokazywane / ukrywane, ten kod będzie nadal działał.

@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];
}
datinc
źródło
czy czegoś jeszcze brakuje? Wysokość komórek się nie zmienia :)
DevC
2
Od iOS 7.1 musisz także wybrać „Clip to SubViews” w Inspektorze atrybutów - tego mi brakowało :)
DevC
Ponadto wiersz "self.dateOpen =! Self.isDateOpen;" powinno brzmieć „self.isDateOpen =” na początku, a nie „self.dateOpen =”
Peter Johnson
2
[tableView reloadData];Wezwanie nie jest wymagane. Obecnie wyłącza wybór wierszy, ale lepiej jest odznaczyć wiersz w ten sposób:[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
Barry Haanstra
Miałem dziwne efekty animacji, takie jak komórka z datą byłaby pusta po „otwarciu”, ale użycie początkowej i końcowej aktualizacji rozwiązało ten problem. Teraz ładnie i gładko. Dzięki datinc!
nh32rg
5

Pobrałem źródło DateCell od Apple i usunąłem plik storyboardu.

Jeśli chcesz taki bez storyboardu, spójrz na: https://github.com/ajaygautam/DateCellWithoutStoryboard

Ajay Gautam
źródło
Dzięki @Ajay użyłem swój zmieniony kod i chcesz wymienić UIDatePickersię UIPickerView. Próbowałem kilku rzeczy i jestem w stanie pokazać je pickerVieww każdej komórce, ale nie jest aktualizowana z każdą komórką. Pisałem na moje pytanie i kod tutaj . Proszę spojrzeć i byłoby bardzo miło, gdybyś mógł zaproponować mi rozwiązanie.
Mughees Musaddiq
Widziałem twoje pytanie ... zawiera zbyt wiele informacji i tak naprawdę niewiele wyjaśnia. Być może możesz gdzieś przesłać swój kod / spakować go, abym mógł rzucić okiem. Mogę go zdebugować - jeśli mogę uruchomić kod ...
Ajay Gautam
Dzięki, jakoś udało się wymienić UIDatePickerz UIPickerViewale z kilku błędów. 1. Awaria podczas otwierania UIPickerViewi przewijania tabeli. 2. Automatycznie przypisuje wartość do opcji UILabelSzczegóły w dolnych wierszach tabeli, gdy wartości są przypisywane do etykiety Szczegóły w górnych wierszach. Oto mój kod
Mughees Musaddiq
Czy jesteś w stanie znaleźć testProject, masz szczęście?
Mughees Musaddiq
Przepraszam, jestem trochę zajęty. Nadal potrzebujesz pomocy?
Ajay Gautam
5

Jednym z najlepszych samouczków na ten temat jest UIDatePicker w systemie iOS 7 - część 2 . Zasadniczo używam tutaj statycznych komórek widoku tabeli i implementuję kilka dodatkowych metod. Użyłem do tego Xamarin i C #:

Musisz być aktywny Clip Subviews.

Ustawienie wysokości:

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

    return base.GetHeightForRow(tableView,indexPath);
}

Niż zmienna klasowa: private bool datePickerIsShowing = false;

Pokaż selektor dat:

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

Ukryj selektor dat:

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

I wywołując te funkcje:

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

    this.TableView.DeselectRow (indexPath, true);
}
testowanie
źródło
2
Jeśli głosujesz przeciw, być może możesz wyjaśnić, dlaczego głosujesz przeciw. Zamiast umieszczać linki do stron, zamieściłem też kod. W moim przypadku jest to C #. Nie wiem, co w tym złego.
testowanie
3

Stworzyłem własny, niestandardowy kontroler widoku, aby uprościć proces dodawania wbudowanego selektora w widoku tabeli. Po prostu tworzysz podklasę i przestrzegasz kilku prostych zasad, a on obsługuje prezentację selektora dat.

Możesz go znaleźć tutaj wraz z przykładowym projektem, który pokazuje, jak go używać: https://github.com/ale84/ALEInlineDatePickerViewController

ale84
źródło
3

Znalazłem odpowiedź na usterkę w przykładzie komórki datecell firmy Apple, w której musisz mieć wiersz poniżej ostatniej komórki danych lub pojawia się błąd. W metodzie CellForRowAtIndexPath zamień wiersz ItemData na

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

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

Po wymianie przykładowego kodu mogę teraz wyświetlić komórkę datePicker bez umieszczania komórki pod nią.

Właśnie dołączyłem do stackoverflow, więc jeśli to jest w złym miejscu lub gdzie indziej, przepraszam.

pjmarshall1
źródło
3

Odpowiedź Aarona Bratchera zadziałała, chyba że została użyta z wieloma sekcjami. Animacje były nieco nierówne i nie zsuwało to zbyt dobrze kolejnych sekcji. Aby to naprawić, przejrzałem kolejny zestaw sekcji i przetłumaczyłem wiersze w dół o taką samą wartość, jak wysokość selektora dat.

Edytowałem didSelectRowAtIndexPath na:

// 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];
    }];
}
Timothy Logan
źródło
Dzięki @Timmahh, znalazłem też przerywaną animację sekcji. Z powodzeniem wykorzystałem Twoje rozwiązanie w mojej pierwszej aplikacji Swift! Okazało się, że cellForRowAtIndexPath zwraca nil, gdy komórki są poza ekranem. Aby tego uniknąć, dodałem kolekcję IB outlet zawierającą wszystkie moje komórki, które pojawiły się po komórce selektora i iterowałem przez nie z nowym y_coord. Wydaje się to jednak nieelastyczne, ponieważ muszę aktualizować kolekcję podczas dodawania nowych komórek.
Josh
Masz rację. W rzeczywistości naprawiłem problem, ale przepełnienie stosu nie pozwoliło mi edytować kodu, który tam umieściłem.
Timothy Logan
3

Dodanie do poprzednich odpowiedzi,

Wypróbowałem rozwiązania @datinc i @Aaron Bratcher, oba działały świetnie, ale animacja nie była tak czysta w zgrupowanym statycznym tableView.

Po krótkiej zabawie dotarłem do tego kodu, który działa czysto i świetnie dla mnie -

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

Główną zmianą jest użycie -

        [super tableView:tableView heightForRowAtIndexPath:indexPath];

aby zaktualizować wiersz, w ten sposób pozostałe sekcje tabeli i komórki nie będą animowane.

Mam nadzieję, że to komuś pomoże.

Shani

shannoga
źródło
To bardzo pomogło; Dziękuję Ci! W szczególności aspekt [super tableView], dołączony do posta Aarona Bratchera, przyniósł mi wyniki, których potrzebowałem na iOS 9.2. Jeszcze raz dziękuję!
Terrance Shaw
2

Dodawanie do poprzednich odpowiedzi i rozwiązania @Aaron Bratcher ...

Od czasu iOS 9 pojawiały się zacinające się animacje, a ładowanie stołu trwało trochę i było wystarczająco denerwujące. Zawęziłem to do tego, że selektory dat wolno ładują się z storyboardu. Dodanie selektorów programowo, a nie w scenorysie, poprawiło wydajność ładowania, a jako produkt uboczny animacja jest płynniejsza.

Usuń selektor dat z serii ujęć i miej pustą komórkę, dla której ustawiasz wysokość tak jak w poprzednich odpowiedziach, a następnie wywołaj inicjalizację na 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];
}

Następnie zrealizuj akcję np

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

To ładuje tabelę znacznie szybciej niż poprzednio. Możesz również usunąć linię animacji z, didSelectRowAtIndexPathponieważ animuje płynnie bez niej (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;
    }
}
matt_s
źródło
1

Użycie tej odpowiedzi bez animacji działa poprawnie w iOS 8.1. Przekształciłem go w Swift poniżej:

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()
    }
}
davidmytton
źródło
1

Oto inny sposób rozwiązania problemu bez statycznych liczb stałych. Wszystkie komórki mogą być używane w statycznych i dynamicznych widokach tabel. Ta metoda używa pojedynczej komórki zarówno dla wyboru tytułu, jak i daty!

Przy okazji możesz mieć dowolną liczbę selektorów daty w swoim stole!

Utwórz podklasę UITableViewCell :

Wszystkie komórki widoku tabeli muszą być dziedziczone z tej klasy, a wysokość komórki należy ustawić ręcznie dla każdego wiersza.

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

Utwórz klasę CPDatePickerTableViewCell z naszego 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 () <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

W Twoim widoku kontroler zaimplementuj te dwie metody delegatów

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

Przykład ustawiania ograniczeń w programie Interface Builder

Konstruktor interfejsów

Dodatkowo napisałem niestandardowe klasy komórek dla UITextField i UITextView, gdzie tableView: didSelectRowAtIndexPath: jest wywoływana po wybraniu komórki!

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

Wysokość komórki jest dynamiczna, a wiersz wzrośnie, gdy tekst zostanie zawinięty do nowego wiersza!

//
//  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
Ako
źródło
0

Najłatwiejszy sposób użycia DateCell w wersji Swift: użyj tego przykładu .

  1. Otwórz ten przykład i przetestuj go (Make Xcode to Convert to Swift 2)
  2. Przeciągnij klasęDateCellTableViewController.swift ” do projektu.

  3. Otwórz „Main.storyboard” i skopiuj obiekt ViewController „DateCell” i wklej go do swojego storyboardu.

Ahmed Lotfy
źródło