2

I'm trying to create a UITableViewController where the loading animation on the tableview should make the items slide in from the side. The cells have a higher height than default cells, about 5-7x bigger. I would like an animation like the Google+ loading animation, seen on android. (Card animation).

What I imagine would be to subclass the TableView class, override a loading method that displays new items when they are introduced onto the display, and then use a spring animation to simultaneously rotate the cell while its x and y values are gradually changed.

I tried this code in this answer but didn't quite get it to work. Elaboration and explanation on that answer would also be very helpful. Code:

- (void)animate
{
[[self.tableView visibleCells] enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) {
    [cell setFrame:CGRectMake(320, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
    [UIView animateWithDuration:1 animations:^{
        [cell setFrame:CGRectMake(0, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
    }];
}];
}

Any help will be much appreciated! Erik

Community
  • 1
  • 1
Erik
  • 2,500
  • 6
  • 28
  • 49

2 Answers2

3

Here's a fully functional example of what (I believe) you're describing. I opened an empty single view project, deleted the view control in the storyboard, and simply created a UITableViewController and set its class to the class of a UITableViewController subclass i've created below.

There's nothing added to the header file, so i've excluded it

#import "weeeTables.h"

@interface weeeTables ()


@property (nonatomic, strong) NSMutableSet *shownIndexes;
@property (nonatomic, assign) CATransform3D initialTransform;

@end

The shownIndexes will contain in indexes of the views already displayed, so we don't repeat the animation scrolling back up. The initialTransform property is the transform for the initial state of the cell (where you want it before it comes on screen)

@implementation weeeTables

- (void)viewDidLoad {
[super viewDidLoad];

CGFloat rotationAngleDegrees = -30;
CGFloat rotationAngleRadians = rotationAngleDegrees * (M_PI/180);
CGPoint offsetPositioning = CGPointMake(-20, -20.0);

CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngleRadians, -50.0, 0.0, 1.0);
transform = CATransform3DTranslate(transform, offsetPositioning.x, offsetPositioning.y, -50.0);
_initialTransform = transform;

_shownIndexes = [NSMutableSet set];
}

in ViewDidLoad, we setup the values for the initial transform, I played with the values a bit, and encourage you to do the same to get the effect you're looking for, but the cells animate in from the lower left corner currently.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}

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

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 385;
}




- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"weeCell" forIndexPath:indexPath];


return cell;
}

everything in the block above is pretty typical for a UITableViewController subclass, and has no particular relevance to adding the animation, I just set some static numbers to add plenty of cells to scroll through, the only unique value here is the cell identifier, which as you can see has a very carefully thought out name.

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

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

    [self.shownIndexes addObject:indexPath];
    UIView *weeeeCell = [cell contentView];

    weeeeCell.layer.transform = self.initialTransform;
    weeeeCell.layer.opacity = 0.8;

    [UIView animateWithDuration:.65 delay:0.0 usingSpringWithDamping:.85 initialSpringVelocity:.8 options:0 animations:^{
        weeeeCell.layer.transform = CATransform3DIdentity;
        weeeeCell.layer.opacity = 1;
    } completion:^(BOOL finished) {}];
}
}
@end

Here's the guy that really puts it all together, first we check to see if the cell we're about to display is already in the list of shownIndexes, if it isn't we add it, then create a local variable for this current cells content view.

Then set the transform on that contentView to our initial transform (that we setup in ViewDidLoad). That moves the cell off to our starting position before it's visible to the user, then we animate that transform back to the identity transform. I've got opacity in there too, just for hell of it.

Hope it helps!

domitall
  • 655
  • 5
  • 15
  • Currently at school, will try it tonight ;) – Erik Oct 17 '14 at 07:10
  • Works like a charm :) Is there any way I can alter this animation? Are there any alternatives? I see we set CATransform3DIdentity, but can I change this to something else which would produce a different animation? – Erik Oct 17 '14 at 13:29
  • Sure, that's just the route I chose in this example because of the flexibility. The main idea is a two step process, position the cell (or in this case, the content view inside of the cell) somewhere you want to begin the animation from, and then animate that cell into position. Both of these steps would be done in the willDisplayCell method. @james_womack's response, that's also a perfectly valid way to animate the cells into position. He's just moving the entire cell frame off the screen, then using the UIView animation api to animate it into place. – domitall Oct 17 '14 at 14:04
  • hello again, I've got a small problem with your code. I can't tap any row before the animation is fully completed. Meaning the user may have to tap it several times - which would be annoying. Any idea on how to fix this? – Erik Oct 21 '14 at 07:26
  • Just 2 years late, but I guess this answers your question @Erik, you can use `UIViewAnimationOptionAllowUserInteraction` as options in the animation. Hope this helps someone. – Pablo A. Jun 29 '16 at 09:09
1

I would try using tableView:willDisplayCell:forRowAtIndexPath:

You can set the UITableViewCell to an initial off-screen frame and then asynchronously initialize an animation for that cell.

I've tested this and it works using the Xcode Master/Detail template

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        let frame = CGRectMake(-cell.frame.size.width, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)
        cell.frame = frame

        UIView.animateWithDuration(1, delay: 0.1, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
            let endFrame = CGRectMake(100, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)
            cell.frame = endFrame
        }) { (foo:Bool) -> Void in
            //
        }
    }

You'll just need to add special casing for different presentation cases. This method will be called when you come back to the view again sometimes, or after the cells are scrolled off the view and back on again.

james_womack
  • 10,028
  • 6
  • 55
  • 74