Miej reloadData dla animacji UITableView podczas zmiany

183

Mam UITableView, który ma dwa tryby. Kiedy przełączamy się między trybami, mam inną liczbę sekcji i komórek na sekcję. Idealnie byłoby zrobić fajną animację, gdy stół rośnie lub kurczy się.

Oto kod, który próbowałem, ale nic nie robi:

CGContextRef context = UIGraphicsGetCurrentContext(); 
[UIView beginAnimations:nil context:context]; 
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; 
[UIView setAnimationDuration:0.5]; 

[self.tableView reloadData];
[UIView commitAnimations];

Wszelkie przemyślenia na temat tego, jak to zrobić?

Vertexwahn
źródło

Odpowiedzi:

400

W rzeczywistości jest to bardzo proste:

[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];

Z dokumentacji :

Wywołanie tej metody powoduje, że widok tabeli pyta źródło danych o nowe komórki dla określonych sekcji. Widok tabeli animuje wstawianie nowych komórek, gdy animuje stare komórki.

dmarnel
źródło
13
I po prostu najlepsza odpowiedź na tej stronie!
Matthias D
41
Nie jest to do końca poprawne, ponieważ spowoduje to przeładowanie tylko pierwszej sekcji. Jeśli twój stół ma wiele sekcji, to nie zadziała
Nosrettap
4
Możliwe, że dostaniesz wyjątek, jeśli żadne dane się nie zmieniły. Zobacz moją odpowiedź
Matej
8
@Nosrettap: jeśli chcesz mieć więcej lub wszystkie sekcje tabeli przeładuj ponownie, wszystko, co musisz zrobić, to rozszerzyć NSIndexSet o wszystkie indeksy sekcji, które również wymagają odświeżenia
JonEasy
Wersja _tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Fade)szybka : W szczególności aktualizuje tylko sekcję 0, która jest domyślną pierwszą sekcją.
kbpontius
264

Możesz użyć:

Cel C

[UIView transitionWithView: self.tableView
                  duration: 0.35f
                   options: UIViewAnimationOptionTransitionCrossDissolve
                animations: ^(void)
 {
      [self.tableView reloadData];
 }
                completion: nil];

Szybki

UIView.transitionWithView(tableView,
                          duration: 0.35,
                          options: .TransitionCrossDissolve,
                          animations:
{ () -> Void in
    self.tableView.reloadData()
},
                          completion: nil);

Swift 3, 4 i 5

UIView.transition(with: tableView,
                  duration: 0.35,
                  options: .transitionCrossDissolve,
                  animations: { self.tableView.reloadData() }) // left out the unnecessary syntax in the completion block and the optional completion parameter

Bez kłopotów. :RE

Możesz także użyć dowolnego z nich UIViewAnimationOptionTransitions, aby uzyskać fajniejsze efekty:

  • transitionNone
  • transitionFlipFromLeft
  • transitionFlipFromRight
  • transitionCurlUp
  • transitionCurlDown
  • transitionCrossDissolve
  • transitionFlipFromTop
  • transitionFlipFromBottom
Kenn Cal
źródło
2
Jest to przydatne, jeśli stan początkowy / końcowy widoku tabeli będzie bardzo różny (i obliczenie sekcji i wierszy do dodania / usunięcia byłoby skomplikowane), ale różdżka jest mniej wstrząsająca niż ponowne ładowanie animacji.
Ben Packard
1
To nie jest tak ładna animacja jak przeładowanie widoku tabeli za pomocą wbudowanych metod, ale w przeciwieństwie do wyżej ocenianej metody, ta działa, gdy masz wiele sekcji.
Mark Bridges,
1
Wow, po wypróbowaniu całej reszty, jest to dla mnie zdecydowanie idealne
Lucas Goossen
1
@ MarkBridges wyżej oceniana odpowiedź działa z wieloma sekcjami :) -[_tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withRowAnimation:UITableViewRowAnimationFade];
lobianco
2
@Anthony To nie działa, jeśli stan końcowy ma więcej / mniej sekcji niż stan początkowy. Następnie musisz ręcznie śledzić, które sekcje zostały dodane / usunięte, co jest dość kłopotliwe.
nikolovski
78

Więcej wolności dzięki CATransitionklasie.

Nie ogranicza się to do zanikania, ale może również wykonywać ruchy.


Na przykład:

(nie zapomnij zaimportować QuartzCore)

CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.fillMode = kCAFillModeForwards;
transition.duration = 0.5;
transition.subtype = kCATransitionFromBottom;

[[self.tableView layer] addAnimation:transition forKey:@"UITableViewReloadDataAnimationKey"];

Zmień według typewłasnych potrzeb, np kCATransitionFade.

Wdrożenie w Swift:

let transition = CATransition()
transition.type = kCATransitionPush
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.fillMode = kCAFillModeForwards
transition.duration = 0.5
transition.subtype = kCATransitionFromTop
self.tableView.layer.addAnimation(transition, forKey: "UITableViewReloadDataAnimationKey")
// Update your data source here
self.tableView.reloadData()

Odniesienie do CATransition

Swift 5:

let transition = CATransition()
transition.type = CATransitionType.push
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.fillMode = CAMediaTimingFillMode.forwards
transition.duration = 0.5
transition.subtype = CATransitionSubtype.fromTop
self.tableView.layer.add(transition, forKey: "UITableViewReloadDataAnimationKey")
// Update your data source here
self.tableView.reloadData()
Matej
źródło
animuje to tabelę jako całość, zamiast pojedynczych wierszy / sekcji.
Agos
@Agos Nadal odpowiada na pytanie. Pytanie „Jak ponownie załadować tylko jeden wiersz” ma zupełnie inną odpowiedź, na przykład zastosowanie animacji do layerwłaściwości a UITableViewCell.
Matej
@matejkramny Spodziewałem się, że tabela będzie animować tylko różne wiersze (jak wspomniano w pytaniu), ale ta metoda wypycha całą tabelę na raz. Może czegoś mi brakuje?
Agos
@Agos hmm nie, to powinno to zrobić. Nie może zrobić tylko połowy stołu, ponieważ QuartzCore bezpośrednio modyfikuje widok. Możesz spróbować pobrać komórki z widoku tabeli, a następnie zastosować tę animację do każdej (ale nie jestem pewien, czy to zadziała)
Matej,
2
działało jak urok z kCATransitionFade Dzięki! :)
quarezz
60

Wierzę, że możesz po prostu zaktualizować strukturę danych, a następnie:

[tableView beginUpdates];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES];
[tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES];
[tableView endUpdates];

Ponadto „withRowAnimation” nie jest dokładnie wartością logiczną, ale stylem animacji:

UITableViewRowAnimationFade,
UITableViewRowAnimationRight,
UITableViewRowAnimationLeft,
UITableViewRowAnimationTop,
UITableViewRowAnimationBottom,
UITableViewRowAnimationNone,
UITableViewRowAnimationMiddle
Tiago Fael Matos
źródło
Proste rzeczy, ale tak łatwo przejść bezpośrednio do StackOverflow i uzyskać potrzebny fragment, niż trałować dokumenty. . Dzięki!
Jasper Blues
24

Wszystkie te odpowiedzi zakładają, że używasz UITableView z tylko 1 sekcją.

Aby dokładnie obsłużyć sytuacje, w których masz więcej niż 1 sekcję, użyj:

NSRange range = NSMakeRange(0, myTableView.numberOfSections);
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
[myTableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];

(Uwaga: upewnij się, że masz więcej niż 0 sekcji!)

Inną rzeczą do odnotowania jest to, że możesz napotkać wyjątek NSInternalInconsistencyException, jeśli spróbujesz jednocześnie zaktualizować źródło danych za pomocą tego kodu. W takim przypadku możesz użyć logiki podobnej do tej:

int sectionNumber = 0; //Note that your section may be different

int nextIndex = [currentItems count]; //starting index of newly added items

[myTableView beginUpdates];

for (NSObject *item in itemsToAdd) {
    //Add the item to the data source
    [currentItems addObject:item];

    //Add the item to the table view
    NSIndexPath *path = [NSIndexPath indexPathForRow:nextIndex++ inSection:sectionNumber];
    [myTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationAutomatic];
}

[myTableView endUpdates];
diadyne
źródło
3
Dobra uwaga na temat obliczeń sekcji, ale miałem tylko jedną sekcję, a twój kod zawierał błąd „jeden po drugim”. Musiałem zmienić pierwszy wiersz kodu na zakres NSRange = NSMakeRange (0, myTableView.numberOfSections);
Danyal Aytekin
18

Podejściem do tego jest nakazanie tableView usunięcia i dodania wierszy i sekcji za pomocą

insertRowsAtIndexPaths:withRowAnimation:,
deleteRowsAtIndexPaths:withRowAnimation:,
insertSections:withRowAnimation:I
deleteSections:withRowAnimation:

metody UITableView.

Gdy wywołasz te metody, tabela będzie animować / zamykać żądane elementy, a następnie wywołać reloadData na sobie, abyś mógł zaktualizować stan po tej animacji. Ta część jest ważna - jeśli wszystko ożywisz, ale nie zmienisz danych zwróconych przez źródło danych tabeli, wiersze pojawią się ponownie po zakończeniu animacji.

Tak więc przepływ aplikacji byłby następujący:

[self setTableIsInSecondState:YES];

[myTable deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES]];

Tak długo, jak metody dataSource tabeli zwracają poprawny nowy zestaw sekcji i wierszy przez zaznaczenie [self tableIsInSecondState](lub cokolwiek innego), osiągnie to oczekiwany efekt.

iKenndac
źródło
16

Nie mogę skomentować najważniejszej odpowiedzi, ale szybka implementacja to:

self.tableView.reloadSections([0], with: UITableViewRowAnimation.fade)

możesz dołączyć tyle sekcji, ile chcesz zaktualizować, w pierwszym argumencie argumentu reloadSections.

Inne animacje dostępne z dokumentów: https://developer.apple.com/reference/uikit/uitableviewrowanimation

zanikanie Wstawiony lub usunięty wiersz lub wiersze znikną w widoku tabeli lub z niego.

prawo Wstawiony rząd lub rzędy wsuwają się z prawej strony; usunięty wiersz lub wiersze przesuwają się w prawo.

lewy Wstawiony rząd lub rzędy przesuwają się od lewej; usunięty wiersz lub wiersze przesuwają się w lewo.

góra Wstawiony rząd lub rzędy wsuwają się od góry; usunięty wiersz lub wiersze przesuwają się w górę.

u dołu Wstawiony rząd lub rzędy wsuwają się od dołu; usunięty wiersz lub wiersze przesuwają się w dół.

case none Wstawione lub usunięte wiersze używają domyślnych animacji.

środek Widok tabeli próbuje utrzymać starą i nową komórkę na środku w miejscu, które zrobiły lub zajmą. Dostępne w iPhonie 3.2.

automatyczny Widok tabeli wybiera odpowiedni styl animacji. (Wprowadzone w iOS 5.0.)

Christopher Larsen
źródło
1
Dla Swift 4.2: self.tableView.reloadSections ([0], z: UITableView.RowAnimation.fade)
Reefwing
14

Wersja Swift 4 dla @dmarnel odpowiedź:

tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
chengsam
źródło
9

Szybkie wdrożenie:

let range = NSMakeRange(0, self.tableView!.numberOfSections())
let indexSet = NSIndexSet(indexesInRange: range)
self.tableView!.reloadSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
Michael Peterson
źródło
8

Dla Swift 4

tableView.reloadSections([0], with: UITableView.RowAnimation.fade)
Mikołaj
źródło
1

W moim przypadku chciałem dodać 10 dodatkowych wierszy do widoku tabeli (dla funkcji typu „pokaż więcej wyników”) i wykonałem następujące czynności:

  NSInteger tempNumber = self.numberOfRows;
  self.numberOfRows += 10;
  NSMutableArray *arrayOfIndexPaths = [[NSMutableArray alloc] init];
  for (NSInteger i = tempNumber; i < self.numberOfRows; i++) {
    [arrayOfIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
  }
  [self.tableView beginUpdates];
  [self.tableView insertRowsAtIndexPaths:arrayOfIndexPaths withRowAnimation:UITableViewRowAnimationTop];
  [self.tableView endUpdates];

W większości przypadków zamiast „self.numberOfRows” zwykle użyjesz liczby obiektów w widoku tabeli. Aby upewnić się, że to rozwiązanie działa dobrze, „arrayOfIndexPaths” musi być dokładną tablicą ścieżek indeksu wstawianych wierszy. Jeśli wiersz istnieje dla którejkolwiek z tych ścieżek indeksu, kod może ulec awarii, dlatego należy użyć metody „reloadRowsAtIndexPaths: withRowAnimation:” dla tych ścieżek indeksu, aby uniknąć awarii

Lucas
źródło
1

Jeśli chcesz dodać własne animacje do komórek UITableView, użyj

[theTableView reloadData];
[theTableView layoutSubviews];
NSArray* visibleViews = [theTableView visibleCells];

aby uzyskać tablicę widocznych komórek. Następnie dodaj dowolną niestandardową animację do każdej komórki.

Zapoznaj się z tym, co opublikowałem, aby uzyskać płynną niestandardową animację komórki. https://gist.github.com/floprr/1b7a58e4a18449d962bd

flopr
źródło
1

Animowanie bez reloadData () w Swift można wykonać w następujący sposób (od wersji 2.2):

tableview.beginUpdates()
var indexPathsToDeleteForAnimation: [NSIndexPath] = []
var numOfCellsToRemove = ArrayOfItemsToRemove ?? 0

// Do your work here
while numOfCellsToRemove > 0 {
    // ...or here, if you need to add/remove the same amount of objects to/from somewhere
    indexPathsToDeleteForAnimation.append(NSIndexPath(forRow: selectedCellIndex+numOfCellsToRemove, inSection: 0))
    numOfCellsToRemove -= 1
}
tableview.deleteRowsAtIndexPaths(indexPathsToDeleteForAnimation, withRowAnimation: UITableViewRowAnimation.Right)
tableview.endUpdates()

na wypadek, gdybyś musiał wywołać reloadData () po zakończeniu animacji, możesz uwzględnić zmiany w CATransaction w następujący sposób:

CATransaction.begin()
CATransaction.setCompletionBlock({() in self.tableview.reloadData() })
tableview.beginUpdates()
var indexPathsToDeleteForAnimation: [NSIndexPath] = []
var numOfCellsToRemove = ArrayOfItemsToRemove.count ?? 0

// Do your work here
while numOfCellsToRemove > 0 {
     // ...or here, if you need to add/remove the same amount of objects to/from somewhere
     indexPathsToDeleteForAnimation.append(NSIndexPath(forRow: selectedCellIndex+numOfCellsToRemove, inSection: 0))
     numOfCellsToRemove -= 1
}
tableview.deleteRowsAtIndexPaths(indexPathsToDeleteForAnimation, withRowAnimation: UITableViewRowAnimation.Right)
tableview.endUpdates()
CATransaction.commit()

Logika jest pokazana w przypadku usuwania wierszy, ale ten sam pomysł działa również w przypadku dodawania wierszy. Możesz także zmienić animację na UITableViewRowAnimation.Left, aby była schludna, lub wybrać z listy innych dostępnych animacji.

Vitalii
źródło
1
CATransition *animation = [CATransition animation];
animation.duration = .3;
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[animation setDuration:.3];

[[_elementTableView layer] addAnimation:animation forKey:@"UITableViewReloadDataAnimationKey"];

[tableView reloadData];
Mayur Sardana
źródło
2
Po co ustawiać czas trwania .3dwa razy?
Pang
1

Aby ponownie załadować wszystkie sekcje , a nie tylko jedną z niestandardowym czasem trwania .

durationParametr użytkownika UIView.animatedla ustawienia niestandardowego czasu trwania.

UIView.animate(withDuration: 0.4, animations: { [weak self] in
    guard let `self` = self else { return }
    let indexSet = IndexSet(integersIn: 0..<self.tableView.numberOfSections)
    self.tableView.reloadSections(indexSet, with: UITableView.RowAnimation.fade)
})
JPetric
źródło
0

Natywne UITableViewanimacje w Swift

Wstawiaj i usuwaj wiersze jednocześnie, tableView.performBatchUpdatesaby występowały jednocześnie. Opierając się na odpowiedzi z metod @iKenndac, takich jak:

  • tableView.insertSections
  • tableView.insertRows
  • tableView.deleteSections
  • tableView.deleteRows

Dawny:

 tableView.performBatchUpdates({
   tableView.insertSections([0], with: .top)
 })

To wstawia sekcję w pozycji zerowej z animacją ładowaną od góry. Spowoduje to ponowne uruchomieniecellForRowAt metody i sprawdzenie, czy w tej pozycji jest nowa komórka. W ten sposób możesz ponownie załadować cały widok tabeli z określonymi animacjami.

Re: pytanie OP, flaga warunkowa byłaby potrzebna, aby pokazać komórki dla alternatywnego stanu widoku tabeli.

rzemiosło
źródło