Czy można animować zmianę wysokości w UITableViewCell, gdy jest wybrana?

393

Korzystam z UITableViewaplikacji w telefonie iPhone i mam listę osób należących do grupy. Chciałbym, aby gdy użytkownik kliknął konkretną osobę (w ten sposób wybierając komórkę), komórka wyrosła na wysokość, aby wyświetlić kilka kontrolek interfejsu użytkownika do edycji właściwości tej osoby.

czy to możliwe?

Mark Amery
źródło

Odpowiedzi:

879

Znalazłem NAPRAWDĘ PROSTE rozwiązanie tego efektu ubocznego, nad którym UITableViewpracowałem .....

Przechowuj wysokość komórki w zmiennej, która normalnie zgłasza pierwotną wysokość tableView: heightForRowAtIndexPath:, a kiedy chcesz animować zmianę wysokości, po prostu zmień wartość zmiennej i wywołaj to ...

[tableView beginUpdates];
[tableView endUpdates];

Przekonasz się, że nie wykonuje pełnego przeładowania, ale wystarczy, UITableViewaby wiedzieć, że musi przerysować komórki, chwytając nową wartość wysokości komórki ... i zgadnij co? ANIMUJE zmianę dla Ciebie. Słodkie.

Na blogu mam bardziej szczegółowe objaśnienia i pełne przykłady kodu ... Animacja zmiany wysokości komórki UITableView

Simon Lee
źródło
6
To jest genialne. Ale czy jest jakiś sposób, aby kontrolować prędkość animacji stołu?
Josh Kahane
7
Działa to, ale jeśli powiększę komórkę, powiedzmy od 44 do 74, a następnie zmniejszę ją ponownie do 44, linia separatora działa zupełnie dziwnie. Czy ktoś może to potwierdzić?
plaetzchen
46
To dziwne rozwiązanie, ale Apple zaleca je również podczas sesji „Mastering Table View” WWDC 2010. Zamierzam złożyć raport o błędzie dotyczący dodania tego do dokumentacji, ponieważ spędziłem około 2 godzin na badaniu.
bpapa
5
Wypróbowałem to rozwiązanie i działa ono tylko czasami, gdy naciśniesz komórkę, istnieje 50% prawdopodobieństwa działania. Czy ktoś ma ten sam błąd? To dlatego, że iOS7?
Joan Cardona
7
Teraz jest także napisane w oficjalnej dokumentacji: You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell. developer.apple.com/library/ios/documentation/UIKit/Reference/…
Jaroslav
63

Podoba mi się odpowiedź Simona Lee. Właściwie nie wypróbowałem tej metody, ale wygląda na to, że zmieniłby rozmiar wszystkich komórek na liście. Miałem nadzieję na zmianę tylko podsłuchiwanej komórki. Zrobiłem to trochę jak Simon, ale z niewielką różnicą. Spowoduje to zmianę wyglądu komórki po jej wybraniu. I to animuje. Po prostu inny sposób na zrobienie tego.

Utwórz int, aby przechowywać wartość dla bieżącego wybranego indeksu komórki:

int currentSelection;

Następnie:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    selectedNumber = row;
    [tableView beginUpdates];
    [tableView endUpdates];
}

Następnie:

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

    if ([indexPath row] == currentSelection) {
        return  80;
    }
    else return 40;


}

Jestem pewien, że możesz dokonać podobnych zmian w tableView: cellForRowAtIndexPath: aby zmienić typ komórki lub nawet załadować plik xib dla komórki.

W ten sposób currentSelection rozpocznie się od 0. Jeśli nie chcesz, aby pierwsza komórka listy (o indeksie 0) domyślnie wyglądała na wybraną, musisz ją wprowadzić.

Mark A.
źródło
2
Sprawdź kod dołączony do mojego posta, dokładnie to zrobiłem, podwojąc wysokość komórki po wybraniu. :)
Simon Lee
8
„Właściwie to nie wypróbowałem tej metody, ale wygląda na to, że zmieniłby rozmiar wszystkich komórek na liście” - wtedy nie wyglądałeś bardzo ciężko.
jamie
13
Bieżący wybór jest już zapisany w tableView.indexPathForSelectedRow.
Nick
22

Dodaj właściwość, aby śledzić wybraną komórkę

@property (nonatomic) int currentSelection;

Ustaw wartość wartownika w (na przykład) viewDidLoad, aby upewnić się, że UITableViewzaczyna się w pozycji „normalnej”

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //sentinel
    self.currentSelection = -1;
}

W heightForRowAtIndexPathmożesz ustawić żądaną wysokość dla wybranej komórki

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    int rowHeight;
    if ([indexPath row] == self.currentSelection) {
        rowHeight = self.newCellHeight;
    } else rowHeight = 57.0f;
    return rowHeight;
}

W didSelectRowAtIndexPathzapisz bieżący wybór i zapisz dynamiczną wysokość, jeśli to konieczne

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        // do things with your cell here

        // set selection
        self.currentSelection = indexPath.row;
        // save height for full text label
        self.newCellHeight = cell.titleLbl.frame.size.height + cell.descriptionLbl.frame.size.height + 10;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}

W didDeselectRowAtIndexPathustawienia tyłem do wyboru wskaźnika wartości wskaźnikowych i ożywić komórek z powrotem do normalnej postaci

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {       
        // do things with your cell here

        // sentinel
        self.currentSelection = -1;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}
Radość
źródło
Dziekuję Dziekuję Dziękuję! Dodałem trochę kodu, aby komórka mogła się przełączać. Dodałem kod poniżej.
Septronic,
14

reloadData nie jest dobra, ponieważ nie ma animacji ...

Właśnie tego próbuję obecnie:

NSArray* paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];

To prawie działa poprawnie. Prawie. Zwiększam wysokość komórki, a czasami w widoku tabeli pojawia się mały „czkawka”, gdy komórka jest zastępowana, tak jakby zachowane zostało pewne przewijane położenie w widoku tabeli, nowa komórka (która jest pierwszą komórką w tabeli) kończy się zbyt dużym przesunięciem, a widok przewijania podskakuje, aby zmienić jego położenie.

Lawrence
źródło
Osobiście odkryłem, że użycie tej metody, ale z UITableViewRowAnimationNone zapewnia gładszy, ale wciąż nie doskonały wynik.
Ron Srebro
11

Nie wiem, co to wszystko o wywoływaniu beginUpdates / endUpdates po kolei, możesz po prostu użyć -[UITableView reloadRowsAtIndexPaths:withAnimation:]. Oto przykładowy projekt .

axiixc
źródło
Doskonale, że to nie rozciąga moich widoków tekstu w komórce automatycznego układu. Ale musi mieć migotanie animacji, ponieważ opcja Brak animacji wygląda nieprawidłowo podczas aktualizacji rozmiaru komórki.
h3dkandi
10

Rozwiązałem z reloadRowsAtIndexPaths.

Zapisać się didSelectRowAtIndexPathdo wybranej komórki indexPath i rozmowy reloadRowsAtIndexPathsna końcu (można wysłać NSMutableArray na liście elementu chcesz reload).

W heightForRowAtIndexPathmożesz sprawdzić, czy indeksPath znajduje się na liście, czy nie w komórce expandIndexPath i wysłać wysokość.

Możesz sprawdzić ten podstawowy przykład: https://github.com/ferminhg/iOS-Examples/tree/master/iOS-UITableView-Cell-Height-Change/celdascambiadetam To proste rozwiązanie.

dodaję kod, jeśli ci pomogę

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath
{
    if ([indexPath isEqual:_expandIndexPath])
        return 80;

    return 40;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Celda";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [cell.textLabel setText:@"wopwop"];

    return cell;
}

#pragma mark -
#pragma mark Tableview Delegate Methods

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSMutableArray *modifiedRows = [NSMutableArray array];
    // Deselect cell
    [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
    _expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];

    // This will animate updating the row sizes
    [tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
}
fermin
źródło
3

Spróbuj wykonać to w celu rozwinięcia wiersza indeksowanego:

@property (nonatomic) NSIndexPath *expandIndexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([indexPath isEqual:self.expandedIndexPath])
    return 100;

return 44;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandIndexPath]) {
    [modifiedRows addObject:self.expandIndexPath];
    self.expandIndexPath = nil;
} else {
    if (self.expandedIndexPath)
        [modifiedRows addObject:self.expandIndexPath];

    self.expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];
}

// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];

// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ViewControllerCellReuseIdentifier];
    cell.textLabel.text = [NSString stringWithFormat:@"I'm cell %ld:%ld", (long)indexPath.section, (long)indexPath.row];

return cell;
}
Nagarjun
źródło
3
BOOL flag;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    flag = !flag;
    [tableView beginUpdates];
    [tableView reloadRowsAtIndexPaths:@[indexPath] 
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView endUpdates];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES == flag ? 20 : 40;
}
Roman Solodyashkin
źródło
2

tylko notatkę dla kogoś takiego jak ja szukającego dodania „Więcej szczegółów” w komórce niestandardowej.

[tableView beginUpdates];
[tableView endUpdates];

Wykonał świetną pracę, ale nie zapomnij „przyciąć” widoku komórki. Od konstruktora Interface zaznaczyć komórki -> Wyświetlanie treści -> z Inspektora właściwości wybierz „ klip podrzędny

Anthony Marchenko
źródło
2

Oto krótsza wersja odpowiedzi Simonsa dla Swift 3. Pozwala również na przełączanie wyboru komórki

var cellIsSelected: IndexPath?


  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    cellIsSelected = cellIsSelected == indexPath ? nil : indexPath
    tableView.beginUpdates()
    tableView.endUpdates()
  }


  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if cellIsSelected == indexPath {
      return 250
    }
    return 65
  }
aBikis
źródło
2

Swift 4 i wyżej

dodaj poniższy kod do swojej metody delegowania wiersza w widoku tabeli

tableView.beginUpdates()
tableView.setNeedsLayout()
tableView.endUpdates()
midhun p
źródło
1

Szybka wersja odpowiedzi Simona Lee.

// MARK: - Variables 
  var isCcBccSelected = false // To toggle Bcc.



    // MARK: UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    // Hide the Bcc Text Field , until CC gets focused in didSelectRowAtIndexPath()
    if self.cellTypes[indexPath.row] == CellType.Bcc {
        if (isCcBccSelected) {
            return 44
        } else {
            return 0
        }
    }

    return 44.0
}

Następnie w didSelectRowAtIndexPath ()

  func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.tableView.deselectRowAtIndexPath(indexPath, animated: true)

    // To Get the Focus of CC, so that we can expand Bcc
    if self.cellTypes[indexPath.row] == CellType.Cc {

        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? RecipientTableViewCell {

            if cell.tag == 1 {
                cell.recipientTypeLabel.text = "Cc:"
                cell.recipientTextField.userInteractionEnabled = true
                cell.recipientTextField.becomeFirstResponder()

                isCcBccSelected = true

                tableView.beginUpdates()
                tableView.endUpdates()
            }
        }
    }
}
ioopl
źródło
1

Tak, to możliwe.

UITableView ma metodę delegowania didSelectRowAtIndexPath

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [UIView animateWithDuration:.6
                          delay:0
         usingSpringWithDamping:UIViewAnimationOptionBeginFromCurrentState
          initialSpringVelocity:0
                        options:UIViewAnimationOptionBeginFromCurrentState animations:^{

                            cellindex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
                            NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
                            [violatedTableView beginUpdates];
                            [violatedTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
                            [violatedTableView endUpdates];
                        }
                     completion:^(BOOL finished) {
    }];
}

Ale w twoim przypadku, jeśli użytkownik przewinie i wybierze inną komórkę, musisz mieć ostatnią wybraną komórkę, aby zmniejszyć i rozwinąć aktualnie wybrane reloadRowsAtIndexPaths:połączenia komórkowe, heightForRowAtIndexPath:więc odpowiednio postępuj.

Koushik
źródło
0

Oto mój kod niestandardowej UITableViewpodklasy, która rozwija się UITextVieww komórce tabeli, bez ponownego ładowania (i utraty skupienia klawiatury):

- (void)textViewDidChange:(UITextView *)textView {
    CGFloat textHeight = [textView sizeThatFits:CGSizeMake(self.width, MAXFLOAT)].height;
    // Check, if text height changed
    if (self.previousTextHeight != textHeight && self.previousTextHeight > 0) {
        [self beginUpdates];

        // Calculate difference in height
        CGFloat difference = textHeight - self.previousTextHeight;

        // Update currently editing cell's height
        CGRect editingCellFrame = self.editingCell.frame;
        editingCellFrame.size.height += difference;
        self.editingCell.frame = editingCellFrame;

        // Update UITableView contentSize
        self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height + difference);

        // Scroll to bottom if cell is at the end of the table
        if (self.editingNoteInEndOfTable) {
            self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y + difference);
        } else {
            // Update all next to editing cells
            NSInteger editingCellIndex = [self.visibleCells indexOfObject:self.editingCell];
            for (NSInteger i = editingCellIndex; i < self.visibleCells.count; i++) {
                UITableViewCell *cell = self.visibleCells[i];
                CGRect cellFrame = cell.frame;
                cellFrame.origin.y += difference;
                cell.frame = cellFrame;
            }
        }
        [self endUpdates];
    }
    self.previousTextHeight = textHeight;
}
Witalij Gozhenko
źródło
0

Użyłem niesamowitej odpowiedzi @ Joy, która zadziałała doskonale w systemie iOS 8.4 i XCode 7.1.1.

Jeśli chcesz, aby Twoja komórka mogła się przełączać, zmieniłem -tableViewDidSelect na następujące:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//This is the bit I changed, so that if tapped once on the cell, 
//cell is expanded. If tapped again on the same cell, 
//cell is collapsed. 
    if (self.currentSelection==indexPath.row) {
        self.currentSelection = -1;
    }else{
        self.currentSelection = indexPath.row;
    }
        // animate
        [tableView beginUpdates];
        [tableView endUpdates];

}

Mam nadzieję, że coś z tego pomogło.

Septronic
źródło
0

Sprawdź tę metodę po iOS 7 i nowszych.

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewAutomaticDimension;
}

Ulepszenia wprowadzono w iOS 8. Możemy ustawić go jako właściwość samego widoku tabeli.

Govind
źródło
0

Szybka wersja odpowiedzi Simona Lee :

tableView.beginUpdates()
tableView.endUpdates()

Należy pamiętać, że należy zmienić właściwości wysokości PRZED endUpdates() .

Tamás Sengel
źródło
0

Wejścia -

tableView.beginUpdates () tableView.endUpdates () te funkcje nie będą wywoływane

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}

Ale jeśli to zrobisz, tableView.reloadRows (at: [selectedIndexPath! As IndexPath], z: .none)

Będzie ona wywołać tableView funkcjono (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} funkcja.

sRoy
źródło
-1

Właśnie rozwiązałem ten problem za pomocą małego włamania:

static int s_CellHeight = 30;
static int s_CellHeightEditing = 60;

- (void)onTimer {
    cellHeight++;
    [tableView reloadData];
    if (cellHeight < s_CellHeightEditing)
        heightAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(onTimer) userInfo:nil repeats:NO] retain];
}

- (CGFloat)tableView:(UITableView *)_tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
        if (isInEdit) {
            return cellHeight;
        }
        cellHeight = s_CellHeight;
        return s_CellHeight;
}

Kiedy muszę zwiększyć wysokość komórki, ustawiam isInEdit = YESi [self onTimer]wywołuję metodę, która animuje wzrost komórki, aż osiągnie wartość s_CellHeightEditing :-)

Dzamir
źródło
W symulatorze działa świetnie, ale w iPhonie sprzęt jest opóźniony. Z opóźnieniem czasowym 0,05 i wzrostem komórki o 5 jednostek jest znacznie lepiej, ale nic takiego jak CoreAnimation
Dzamir
1
Potwierdź przed wysłaniem.
ajay_nasa,
-2

Uzyskaj ścieżkę indeksu wybranego wiersza. Załaduj ponownie stół. W metodzie heightForRowAtIndexPath metody UITableViewDelegate ustaw wysokość wybranego wiersza na inną wysokość, a dla innych zwróć normalną wysokość wiersza

lostInTransit
źródło
2
-1, nie działa. Wywołanie [table reloadData]powoduje natychmiastową zmianę wysokości, a nie animację.
Mark Amery