Erik
Erik

Reputation: 2530

Custom loading animation for UITableView

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

Upvotes: 2

Views: 3153

Answers (2)

domitall
domitall

Reputation: 655

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!

Upvotes: 3

james_womack
james_womack

Reputation: 10296

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.

Upvotes: 1

Related Questions