Table view cell load animation one after another
Asked Answered
L

7

19

I need to animate the load of the table view rows. When the table reloads the data I need the rows get in from the left one after another. How can I achieve this?

Lamrert answered 29/10, 2015 at 9:34 Comment(0)
F
28

In your tableview delegate,

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

Put this bottom-to-top fade-in translation animation (Simplified from Anbu.Karthik answer),

    //1. Define the initial state (Before the animation)
    cell.transform = CGAffineTransformMakeTranslation(0.f, CELL_HEIGHT);
    cell.layer.shadowColor = [[UIColor blackColor]CGColor];
    cell.layer.shadowOffset = CGSizeMake(10, 10);
    cell.alpha = 0;

    //2. Define the final state (After the animation) and commit the animation
    [UIView beginAnimations:@"rotation" context:NULL];
    [UIView setAnimationDuration:0.5];
    cell.transform = CGAffineTransformMakeTranslation(0.f, 0);
    cell.alpha = 1;
    cell.layer.shadowOffset = CGSizeMake(0, 0);
    [UIView commitAnimations];

For better UX, it is advised that the animation should only be played once for each row, until the table view is dealloc-ed.

Put the above code in

if (![self.shownIndexes containsObject:indexPath]) {
    [self.shownIndexes addObject:indexPath];

    // Your animation code here.
}

------- Swift -----------------------------------------------------------------------------------------------------------------

var shownIndexes : [IndexPath] = []
let CELL_HEIGHT : CGFloat = 40

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if (shownIndexes.contains(indexPath) == false) {
        shownIndexes.append(indexPath)

        cell.transform = CGAffineTransform(translationX: 0, y: CELL_HEIGHT)
        cell.layer.shadowColor = UIColor.black.cgColor
        cell.layer.shadowOffset = CGSize(width: 10, height: 10)
        cell.alpha = 0

        UIView.beginAnimations("rotation", context: nil)
        UIView.setAnimationDuration(0.5)
        cell.transform = CGAffineTransform(translationX: 0, y: 0)
        cell.alpha = 1
        cell.layer.shadowOffset = CGSize(width: 0, height: 0)
        UIView.commitAnimations()
    }
}
Fanni answered 19/1, 2016 at 3:56 Comment(2)
I want to two animate two default custom cells.Intially one cell is shown in the tableview and after 10 seconds another cell will appear on the tableview.But now second cell on the top of tableview and and first cell shift downward.How to achieve this.Please help me out if you have any idea.Densitometer
You can use [UIView animateWithDuration:0.5 delay:10.0 options: UIViewAnimationOptionTransitionCrossDissolve animations:^{ // animation starts } completion:nil]; }];Fanni
M
38

Swift 4

Add this little cute extension

extension UITableView {    

    func reloadWithAnimation() {
        self.reloadData()
        let tableViewHeight = self.bounds.size.height
        let cells = self.visibleCells
        var delayCounter = 0
        for cell in cells {
            cell.transform = CGAffineTransform(translationX: 0, y: tableViewHeight)
        }
        for cell in cells {
            UIView.animate(withDuration: 1.6, delay: 0.08 * Double(delayCounter),usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
                cell.transform = CGAffineTransform.identity
            }, completion: nil)
            delayCounter += 1
        }
    }
}

Then, instead of "tableView.reloadData()", use "tableView.reloadWithAnimation()"

Mix answered 30/3, 2018 at 8:12 Comment(1)
Good one!! Thank you.Vanir
L
29

Here's my Swift 3 solution for displaying the cells one by one. what's nice about it is that they load only at first load time, and only for the initially displayed cells (will not run when user scrolls down).

Enjoy :)

private var finishedLoadingInitialTableCells = false

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    var lastInitialDisplayableCell = false

    //change flag as soon as last displayable cell is being loaded (which will mean table has initially loaded)
    if yourTableData.count > 0 && !finishedLoadingInitialTableCells {
        if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows,
            let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row {
            lastInitialDisplayableCell = true
        }
    }

    if !finishedLoadingInitialTableCells {

        if lastInitialDisplayableCell {
            finishedLoadingInitialTableCells = true
        }

        //animates the cell as it is being displayed for the first time
        cell.transform = CGAffineTransform(translationX: 0, y: self.rowHeight/2)
        cell.alpha = 0

        UIView.animate(withDuration: 0.5, delay: 0.05*Double(indexPath.row), options: [.curveEaseInOut], animations: {
            cell.transform = CGAffineTransform(translationX: 0, y: 0)
            cell.alpha = 1
        }, completion: nil)
    }
}
Larrup answered 25/10, 2017 at 9:2 Comment(0)
F
28

In your tableview delegate,

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

Put this bottom-to-top fade-in translation animation (Simplified from Anbu.Karthik answer),

    //1. Define the initial state (Before the animation)
    cell.transform = CGAffineTransformMakeTranslation(0.f, CELL_HEIGHT);
    cell.layer.shadowColor = [[UIColor blackColor]CGColor];
    cell.layer.shadowOffset = CGSizeMake(10, 10);
    cell.alpha = 0;

    //2. Define the final state (After the animation) and commit the animation
    [UIView beginAnimations:@"rotation" context:NULL];
    [UIView setAnimationDuration:0.5];
    cell.transform = CGAffineTransformMakeTranslation(0.f, 0);
    cell.alpha = 1;
    cell.layer.shadowOffset = CGSizeMake(0, 0);
    [UIView commitAnimations];

For better UX, it is advised that the animation should only be played once for each row, until the table view is dealloc-ed.

Put the above code in

if (![self.shownIndexes containsObject:indexPath]) {
    [self.shownIndexes addObject:indexPath];

    // Your animation code here.
}

------- Swift -----------------------------------------------------------------------------------------------------------------

var shownIndexes : [IndexPath] = []
let CELL_HEIGHT : CGFloat = 40

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if (shownIndexes.contains(indexPath) == false) {
        shownIndexes.append(indexPath)

        cell.transform = CGAffineTransform(translationX: 0, y: CELL_HEIGHT)
        cell.layer.shadowColor = UIColor.black.cgColor
        cell.layer.shadowOffset = CGSize(width: 10, height: 10)
        cell.alpha = 0

        UIView.beginAnimations("rotation", context: nil)
        UIView.setAnimationDuration(0.5)
        cell.transform = CGAffineTransform(translationX: 0, y: 0)
        cell.alpha = 1
        cell.layer.shadowOffset = CGSize(width: 0, height: 0)
        UIView.commitAnimations()
    }
}
Fanni answered 19/1, 2016 at 3:56 Comment(2)
I want to two animate two default custom cells.Intially one cell is shown in the tableview and after 10 seconds another cell will appear on the tableview.But now second cell on the top of tableview and and first cell shift downward.How to achieve this.Please help me out if you have any idea.Densitometer
You can use [UIView animateWithDuration:0.5 delay:10.0 options: UIViewAnimationOptionTransitionCrossDissolve animations:^{ // animation starts } completion:nil]; }];Fanni
K
2

None of the solutions provided helped me so I came up with my own. Here's a little general purpose class that can be used to chain animations together and play them one after another. It's syntax is similar to that of the UIView.animate() and once called, asynchronously queues the animation and then begins executing the queue in a sequential manner in the order they were added:

Swift 4.1

ChainedAnimationsQueue.swift

import UIKit
import Foundation

class ChainedAnimationsQueue {

  private var playing = false
  private var animations = [(TimeInterval, () -> Void, () -> Void)]()

  init() {
  }

  /// Queue the animated changes to one or more views using the specified duration and an initialization block.
  ///
  /// - Parameters:
  ///   - duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them.
  ///   - initializations: A block object containing the changes to commit to the views to set their initial state. This block takes no parameters and has no return value. This parameter must not be NULL.
  ///   - animations: A block object containing the changes to commit to the views. This is where you programmatically change any animatable properties of the views in your view hierarchy. This block takes no parameters and has no return value. This parameter must not be NULL.
  func queue(withDuration duration: TimeInterval, initializations: @escaping () -> Void, animations: @escaping () -> Void) {
    self.animations.append((duration, initializations, animations))
    if !playing {
      playing = true
      DispatchQueue.main.async {
        self.next()
      }
    }
  }

  private func next() {
    if animations.count > 0 {
      let animation = animations.removeFirst()
      animation.1()
      UIView.animate(withDuration: animation.0, animations: animation.2, completion: { finished in
        self.next()
      })
    } else {
      playing = false
    }
  }
}

Sample Usage:

var animationsQueue = ChainedAnimationsQueue()

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  cell.alpha = 0.0
  animationsQueue.queue(withDuration: 0.2, initializations: {
    cell.layer.transform = CATransform3DTranslate(CATransform3DIdentity, cell.frame.size.width, 0, 0)
  }, animations: {
    cell.alpha = 1.0
    cell.layer.transform = CATransform3DIdentity
  })
}
Kendalkendall answered 27/7, 2018 at 16:22 Comment(0)
G
2

Swift 4

I made a quick extension on UITableView to animate cells:

tableView.reloadData() // To make sure tableView.visibleCells is not empty

tableView.animateCells(
      cells: tableView.visibleCells,
      duration: 0.3,
      delay: 0.5,
      dampingRatio: 0.8,
      configure: { cell -> (prepare: () -> Void, animate: () -> Void)? in
        guard let customCell = cell as? CustomCell else { return nil }
        let preparations = {
          customCell.iconImageView.alpha = 0
        }
        let animations = {
          customCell.iconImageView.alpha = 1
        }
        return (preparations, animations)
    }, completion: {
      print("Cell animations are completed")
    })

The extension looks like this:

extension UITableView {
  func animateCells<Cell: UITableViewCell>(cells: [Cell],
                                           duration: TimeInterval,
                                           delay: TimeInterval = 0,
                                           dampingRatio: CGFloat = 0,
                                           configure: @escaping (Cell) -> (prepare: () -> Void, animate: () -> Void)?,
                                           completion: @escaping () -> Void) {
    var cellDelay: TimeInterval = 0
    var completionCount: Int = 0

    for cell in cells {
      if let callbacks = configure(cell) {
        callbacks.prepare()

        let animator = UIViewPropertyAnimator(duration: duration, dampingRatio: dampingRatio)

        animator.addAnimations(callbacks.animate)

        let completionTime = cellDelay + (duration * TimeInterval(dampingRatio))

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + completionTime) {
          completionCount += 1
          if completionCount == cells.count {
            completion()
          }
        }

        animator.startAnimation(afterDelay: cellDelay)

        cellDelay += delay
      } else {
        completionCount += 1
      }
    }
  }
}
Gaslight answered 4/7, 2019 at 23:51 Comment(1)
Great answer. Thanks. Worked perfectly.Minica
S
1

This is simple beautiful fade animation Which I mostly I used in my tableview

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cell.alpha = 0
        UIView.animate(withDuration: 1) {
            cell.alpha = 1.0
        }
    }
Saucy answered 17/8, 2019 at 21:43 Comment(1)
This fades all the cells in at the same time.Disparagement
S
0

tableView:willDisplayCell:forRowAtIndexPath method will be called each time a cell is going to be shown, and since they are getting viewed at the same time it means that they are getting called in different threads and you can't tell iOS SDK to call this method for sequentially. So I think the way to get what you want is to set a delay for each cell when it is being showed.

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    CGFloat delay = indexPath.row * yourSupposedAnimationDuration;
    [UIView animateWithDuration:yourSupposedAnimationDuration delay:delay options:UIViewAnimationOptionCurveEaseIn animations:^{  
        //Your animation code
    }completion:^(BOOL finished) {  
        //Your completion Code
    }];
}
Spirometer answered 2/5, 2016 at 6:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.