ios开发列表页波动动画

从2014年那个夏天毕业之后,从事ios开发差不多两年了,由当初的什么都不懂(mac都不会用,哈哈),现在已经慢慢的喜欢上ios开发,喜欢ios的美,现在萌生写技术博客的想法,借此记录自己的成长,同时分享自己的知识和心得,大家共同进步。

看到我们项目里面什么酷炫的动画都没有,显示不了ios开发的美,今天这里就介绍一个简单界面水波动画,(好像是之前看的一个demo)不多说直接上代码:

我这里是用在列表页,列表页ViewController里面的动画代码

//导入头文件
#import "AMWaveTransition.h"
//添加协议UINavigationControllerDelegate

//视图已经出现时
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.navigationController setDelegate:self];
}
//重点代码
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController*)fromVC
                                                 toViewController:(UIViewController*)toVC
{
    if (operation != UINavigationControllerOperationNone) {
        return [AMWaveTransition transitionWithOperation:operation andTransitionType:AMWaveTransitionTypeBounce];
    }
    return nil;
}

- (void)dealloc
{
    [self.navigationController setDelegate:nil];
}

导入两个类
具体代码分别如下:

1、AMWaveTransition.h

//
//  AMWaveTransition.h
//  AMWaveTransition
//
//  Created by Andrea on 11/04/14.
//  Copyright (c) 2015 Fancy Pixel. All rights reserved.
//

@import UIKit;

/**
 * @name AMWaveTransitioning
 * Delegate protocol for AMWaveTransition
 */
@protocol AMWaveTransitioning <NSObject>

/** Visible cells
 *
 * Returns the cells that need to be animated. 
 *
 * @return An array of UIViews
 */
- (NSArray*)visibleCells;

@end

/** @enum AMWaveTransitionType
 *
 * Enum that specifies the type of animation
 */
typedef NS_ENUM(NSInteger, AMWaveTransitionType) {
    /** Smooth transition */
    AMWaveTransitionTypeSubtle,
    /** Springy transition */
    AMWaveTransitionTypeNervous,
    /** Spring transition with looser springs */
    AMWaveTransitionTypeBounce
};

/** @enum AMWaveInteractiveTransitionType
 *
 * Enum that specifies the transition type
 */
typedef NS_ENUM(NSInteger, AMWaveInteractiveTransitionType) {
    /** The transition needs to start from the edge */
    AMWaveTransitionEdgePan,
    /** The transition can start from anywhere */
    AMWaveTransitionFullScreenPan
};

/**
 * @name AMWaveTransition
 * Custom transition between viewcontrollers holding tableviews. Each cell is animated to simulate a 'wave effect'.
 */
@interface AMWaveTransition : NSObject <UIViewControllerAnimatedTransitioning>

/** New transition
 *
 * Returns a AMWaveTransition instance.
 *
 * @param operation The UINavigationControllerOperation that determines the transition type (push or pop)
 */
+ (instancetype)transitionWithOperation:(UINavigationControllerOperation)operation;

/** New transition
 *
 * Returns a AMWaveTransition instance.
 *
 * @param operation The UINavigationControllerOperation that determines the transition type (push or pop)
 * @param type The transition type
 */
+ (instancetype)transitionWithOperation:(UINavigationControllerOperation)operation andTransitionType:(AMWaveTransitionType)type;

/** New transition
 *
 * Returns a AMWaveTransition instance.
 *
 * @param operation The UINavigationControllerOperation that determines the transition type (push or pop)
 */
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation;

/** New transition
 *
 * Returns a AMWaveTransition instance.
 *
 * @param operation The UINavigationControllerOperation that determines the transition type (push or pop)
 * @param type The transition type
 */
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation andTransitionType:(AMWaveTransitionType)type;

/** Attach the interactive gesture
 *
 * Attach the interactive gesture to the navigation controller. This will pop the current view controller when the user swipes from the left edge.
 * Make sure to detach the gesture when done.
 *
 * @param navigationController The UINavigationController that holds the current view controller
 */
- (void)attachInteractiveGestureToNavigationController:(UINavigationController *)navigationController;

/** Detach the interactive gesture
 *
 * Detaches the interactive gesture.
 */
- (void)detachInteractiveGesture;

/**
 * @name AMWaveTransition Properties
 */

/** Operation type
 *
 * Sets the operation type (push or pop)
 */
@property (assign, nonatomic) UINavigationControllerOperation operation;

/** Transition type
 *
 * Sets the transition style
 */
@property (assign, nonatomic) AMWaveTransitionType transitionType;

/** Animation duration
 *
 * Sets the duration of the animation. The whole duration accounts for the maxDelay property.
 */
@property (assign, nonatomic) CGFloat duration;

/** Maximum animation delay
 *
 * Sets the max delay that a cell will wait beofre animating.
 */
@property (assign, nonatomic) CGFloat maxDelay;

/** Inset between view controllers
 *
 * Sets the inset between view controllers. Defaults to 20 points.
 */
@property (assign, nonatomic) CGFloat viewControllersInset;

/** Alpha animation with interactive transition
 *
 * Turn on/off alpha animation with interactive transition. Defaults to NO.
 */
@property (assign, nonatomic) BOOL animateAlphaWithInteractiveTransition;

/** Interactive transition type
 *
 * Sets interactive transition type (edge or fullscreen). Defaults to edge.
 */
@property (assign, nonatomic) AMWaveInteractiveTransitionType interactiveTransitionType;

@end

 2、AMWaveTransition.m

//
//  AMWaveTransitioning.m
//  AMWaveTransitioning
//
//  Created by Andrea on 11/04/14.
//  Copyright (c) 2015 Fancy Pixel. All rights reserved.
//

#import "AMWaveTransition.h"

typedef NS_ENUM(NSInteger, AMWaveTransitionViewControllers) {
    AMWaveTransitionToVC,
    AMWaveTransitionFromVC,
};

@interface AMWaveTransition ()

@property (nonatomic, strong) UIGestureRecognizer *gesture;
@property (nonatomic, weak) UINavigationController *navigationController;
@property (nonatomic, assign) int selectionIndexFrom;
@property (nonatomic, assign) int selectionIndexTo;
@property (nonatomic, assign) CGPoint firstTouch;

@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) NSMutableArray *attachmentsFrom;
@property (nonatomic, strong) NSMutableArray *attachmentsTo;

@end


@interface UITableView (AMWaveTransition)
- (NSArray*)am_visibleViews;
@end


@implementation AMWaveTransition

#define SCREEN_WIDTH ((([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) || ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)) ? [[UIScreen mainScreen] bounds].size.width : [[UIScreen mainScreen] bounds].size.height)

const CGFloat DURATION = 0.65;
const CGFloat MAX_DELAY = 0.15;

- (void)dealloc {
    [self detachInteractiveGesture];
}

- (instancetype)init {
    if ((self = [super init])) {
        [self setup];
        _operation = UINavigationControllerOperationNone;
        _transitionType = AMWaveTransitionTypeNervous;
    }
    return self;
}

+ (instancetype)transitionWithOperation:(UINavigationControllerOperation)operation {
    return [[self alloc] initWithOperation:operation andTransitionType:AMWaveTransitionTypeNervous];
}

- (instancetype)initWithOperation:(UINavigationControllerOperation)operation {
    return [self initWithOperation:operation andTransitionType:AMWaveTransitionTypeNervous];
}

+ (instancetype)transitionWithOperation:(UINavigationControllerOperation)operation andTransitionType:(AMWaveTransitionType)type {
    return [[self alloc] initWithOperation:operation andTransitionType:type];
}

- (instancetype)initWithOperation:(UINavigationControllerOperation)operation andTransitionType:(AMWaveTransitionType)type {
    self = [super init];
    if (self) {
        [self setup];
        _operation = operation;
        _transitionType = type;
    }
    return self;
}

- (void)setup {
    _viewControllersInset = 20;
    _interactiveTransitionType = AMWaveTransitionEdgePan;
    _animateAlphaWithInteractiveTransition = NO;
    _duration = DURATION;
    _maxDelay = MAX_DELAY;
}

- (void)attachInteractiveGestureToNavigationController:(UINavigationController *)navigationController {
    self.navigationController = navigationController;
    if (self.interactiveTransitionType == AMWaveTransitionEdgePan) {
        UIScreenEdgePanGestureRecognizer *recognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self
                                                                                                         action:@selector(handlePan:)];
        [recognizer setEdges:UIRectEdgeLeft];
        self.gesture = recognizer;
    } else {
        UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                                     action:@selector(handlePan:)];
        self.gesture = recognizer;
    }
    [navigationController.view addGestureRecognizer:self.gesture];
    self.animator = [[UIDynamicAnimator alloc]initWithReferenceView:navigationController.view];
    self.attachmentsFrom = [@[] mutableCopy];
    self.attachmentsTo = [@[] mutableCopy];
}

- (void)detachInteractiveGesture {
    UINavigationController *navigationController = self.navigationController;
    [navigationController.view removeGestureRecognizer:self.gesture];
    self.navigationController = nil;
    self.gesture = nil;
    [self.animator removeAllBehaviors];
    self.animator = nil;
}

- (void)handlePan:(UIScreenEdgePanGestureRecognizer *)gesture {
    UINavigationController *navigationController = self.navigationController; // support CLANG_WARN_OBJC_RECEIVER_WEAK
    
    // Starting controller
    UIViewController *fromVC = navigationController.topViewController;
    
    // Controller that will be visible after the pop
    UIViewController<AMWaveTransitioning> *toVC;
    NSInteger index = [navigationController.viewControllers indexOfObject:navigationController.topViewController];
    // The gesture velocity will also determine the velocity of the cells
    float velocity = [gesture velocityInView:navigationController.view].x;
    CGPoint touch = [gesture locationInView:navigationController.view];
    if (index == 0) {
        // Simple attach animation
        touch.x = 0;
        toVC = nil;
    } else if (index != NSNotFound) {
        toVC = (UIViewController<AMWaveTransitioning> *)navigationController.viewControllers[index-1];
    }
    
    NSArray *fromViews = [self visibleCellsForViewController:fromVC];
    NSArray *toViews = [self visibleCellsForViewController:toVC];
    
    if (gesture.state == UIGestureRecognizerStateBegan) {
        if (self.interactiveTransitionType == AMWaveTransitionFullScreenPan) {
            self.firstTouch = touch;
        } else {
            self.firstTouch = CGPointZero;
        }
        [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            // The 'selected' cell will be the one leading the other cells
            if (CGRectContainsPoint([view.superview convertRect:view.frame toView:nil], touch)) {
                self.selectionIndexFrom = (int)idx;
            }
            [self createAttachmentForView:view inVC:AMWaveTransitionFromVC];
        }];
        
        
        // Kick the 'new' cells outside the view
        [navigationController.view insertSubview:toVC.view belowSubview:navigationController.navigationBar];
        toViews = [self visibleCellsForViewController:toVC]; // re-read, because toVC might have been not ready before
        [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            [self kickCellOutside:view];
        }];
        
        [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            CGRect futureRect = view.frame;
            futureRect.origin.x = 0;
            if (CGRectContainsPoint([view.superview convertRect:futureRect toView:nil], touch)) {
                self.selectionIndexTo = (int)idx;
            }
            [self createAttachmentForView:view inVC:AMWaveTransitionToVC];
        }];
        
    } else if (gesture.state == UIGestureRecognizerStateChanged) {
        
        [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            [self changeAttachmentWithIndex:idx
                                     inView:view
                                     touchX:touch.x
                                   velocity:velocity
                                       inVC:AMWaveTransitionFromVC];
        }];
        
        [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            [self changeAttachmentWithIndex:idx
                                     inView:view
                                     touchX:touch.x
                                   velocity:velocity
                                       inVC:AMWaveTransitionToVC];
        }];
        
    } else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) {
        [self.attachmentsFrom enumerateObjectsUsingBlock:^(UIAttachmentBehavior *obj, NSUInteger idx, BOOL *stop) {
            [self.animator removeBehavior:obj];
        }];
        [self.attachmentsFrom removeAllObjects];
        
        [self.attachmentsTo enumerateObjectsUsingBlock:^(UIAttachmentBehavior *obj, NSUInteger idx, BOOL *stop) {
            [self.animator removeBehavior:obj];
        }];
        [self.attachmentsTo removeAllObjects];
        
        if (gesture.state == UIGestureRecognizerStateEnded && velocity > 0) {
            // Complete the transition
            [UIView animateWithDuration:0.3 animations:^{
                [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self completeFromVC:view];
                }];
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self setPresentedFrameForView:view];
                }];
            } completion:^(BOOL finished) {
                toVC.view.backgroundColor = fromVC.view.backgroundColor;
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self animationCompletionForInteractiveTransitionForView:view];
                }];
                
                [navigationController popViewControllerAnimated:NO];
            }];
        } else {
            // Abort
            [UIView animateWithDuration:0.3 animations:^{
                [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self setPresentedFrameForView:view];
                }];
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self completeToVC:view];
                }];
            } completion:^(BOOL finished) {
                // Bring 'silently' the cell back to their place, or the normal pop operation would fail
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self animationCompletionForInteractiveTransitionForView:view];
                }];
                [toVC.view removeFromSuperview];
            }];
            
        }
    }
}

- (void)animationCompletionForInteractiveTransitionForView:(UIView *)view {
    CGRect rect = view.frame;
    rect.origin.x = 0;
    UINavigationController *navigationController = self.navigationController;
    if (navigationController.navigationBar.translucent && !navigationController.navigationBar.hidden) {
        rect.origin.y -= navigationController.navigationBar.frame.origin.y + navigationController.navigationBar.frame.size.height;
    } else {
        rect.origin.y -= [[UIApplication sharedApplication] statusBarFrame].size.height;
    }
    view.frame = rect;
    view.alpha = [self alphaForView:view];
}

- (void)setPresentedFrameForView:(UIView *)view {
    CGRect rect = view.frame;
    rect.origin.x = 0;
    view.frame = rect;
    view.alpha = [self alphaForView:view];
}

- (void)kickCellOutside:(UIView *)view {
    CGRect rect = view.frame;
    rect.origin.x = -SCREEN_WIDTH - self.viewControllersInset;
    UINavigationController *navigationController = self.navigationController;
    if (navigationController.navigationBar.translucent && !navigationController.navigationBar.hidden) {
        rect.origin.y += navigationController.navigationBar.frame.origin.y + navigationController.navigationBar.frame.size.height;
    } else {
        rect.origin.y += [[UIApplication sharedApplication] statusBarFrame].size.height;
    }
    view.alpha = [self alphaForView:view];
    view.frame = rect;
}

- (void)completeToVC:(UIView *)view {
    [self completeTransitionWithView:view inVC:AMWaveTransitionToVC];
}

- (void)completeFromVC:(UIView *)view {
    [self completeTransitionWithView:view inVC:AMWaveTransitionFromVC];
}

- (void)completeTransitionWithView:(UIView *)view inVC:(AMWaveTransitionViewControllers)viewController {
    CGRect rect = view.frame;
    if (viewController == AMWaveTransitionFromVC) {
        rect.origin.x = SCREEN_WIDTH - self.viewControllersInset;
    } else {
        rect.origin.x = -SCREEN_WIDTH - self.viewControllersInset;
    }
    view.frame = rect;
    view.alpha = [self alphaForView:view];
}

- (void)changeAttachmentWithIndex:(NSUInteger)index
                           inView:(UIView *)view
                           touchX:(CGFloat)touchX
                         velocity:(CGFloat)velocity
                             inVC:(AMWaveTransitionViewControllers)viewController {
    int selectionIndex;
    NSInteger correction = 2;
    NSMutableArray *arrayWithAttachments;
    if (viewController == AMWaveTransitionToVC) {
        arrayWithAttachments = self.attachmentsTo;
        selectionIndex = self.selectionIndexTo;
    } else {
        arrayWithAttachments = self.attachmentsFrom;
        selectionIndex = self.selectionIndexFrom;
        correction = -correction;
    }
    
    float delta = touchX - self.firstTouch.x - abs(selectionIndex - (int)index) * velocity / 50;
    
    // Prevent the anchor point from going 'over' the cell
    if (delta > view.frame.origin.x + view.frame.size.width / 2 && viewController == AMWaveTransitionFromVC) {
        delta = view.frame.origin.x + view.frame.size.width / 2 + correction;
    } else if (delta < view.frame.origin.x + view.frame.size.width / 2 && viewController == AMWaveTransitionToVC) {
        delta = view.frame.origin.x + view.frame.size.width / 2 + correction;
    }
    view.alpha = [self alphaForView:view];
    [arrayWithAttachments[index] setAnchorPoint:(CGPoint){delta, [view.superview convertPoint:view.frame.origin toView:nil].y + view.frame.size.height / 2}];
}

- (void)createAttachmentForView:(UIView *)view inVC:(AMWaveTransitionViewControllers)viewController {
    UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:view attachedToAnchor:(CGPoint){0, [view.superview convertPoint:view.frame.origin toView:nil].y + view.frame.size.height / 2}];
    [attachment setDamping:0.4];
    [attachment setFrequency:1];
    [self.animator addBehavior:attachment];
    view.alpha = [self alphaForView:view];
    
    NSMutableArray *arrayWithAttachments;
    if (viewController == AMWaveTransitionToVC) {
        arrayWithAttachments = self.attachmentsTo;
    } else {
        arrayWithAttachments = self.attachmentsFrom;
    }
    
    [arrayWithAttachments addObject:attachment];
}

- (CGFloat)alphaForView:(UIView *)view {
    if (self.animateAlphaWithInteractiveTransition) {
        CGFloat width = SCREEN_WIDTH - self.viewControllersInset;
        CGFloat alpha = (width - fabs(view.frame.origin.x)) * (1 / width);
        return alpha;
    } else {
        return 1.0;
    }
}

#pragma mark - Non interactive transition

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return self.duration + self.maxDelay;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    UIViewController *fromVC;
    if ([[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] isKindOfClass:[UINavigationController class]]) {
        fromVC = (UIViewController*)([(UINavigationController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] visibleViewController]);
    } else {
        fromVC = (UIViewController*)([transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]);
    }
    
    UIViewController *toVC;
    if ([[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] isKindOfClass:[UINavigationController class]]) {
        toVC = (UIViewController*)([(UINavigationController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] visibleViewController]);
    } else {
        toVC = (UIViewController*)([transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]);
    }
    
    [[transitionContext containerView] addSubview:toVC.view];
    
    CGFloat delta;
    if (self.operation == UINavigationControllerOperationPush) {
        delta = SCREEN_WIDTH + self.viewControllersInset;
    } else {
        delta = -SCREEN_WIDTH - self.viewControllersInset;
    }
    
    // Move the destination in place
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    // And kick it aside
    toVC.view.transform = CGAffineTransformMakeTranslation(delta, 0);

    [transitionContext containerView].backgroundColor = fromVC.view.backgroundColor;

    // Trigger the layout of the new cells
    [[transitionContext containerView] layoutIfNeeded];

    // Plain animation that moves the destination controller in place. Once it's done it will notify the transition context
    if (self.operation == UINavigationControllerOperationPush) {
        [toVC.view setTransform:CGAffineTransformMakeTranslation(1, 0)];
        [UIView animateWithDuration:self.duration + self.maxDelay delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [toVC.view setTransform:CGAffineTransformIdentity];
        } completion:^(BOOL finished2) {
            [transitionContext completeTransition:YES];
        }];
    } else {
        [fromVC.view setTransform:CGAffineTransformMakeTranslation(1, 0)];
        [toVC.view setTransform:CGAffineTransformIdentity];
        [UIView animateWithDuration:self.duration + self.maxDelay delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [fromVC.view setTransform:CGAffineTransformMakeTranslation(0, 0)];
        } completion:^(BOOL finished2) {
            [fromVC.view removeFromSuperview];
            [transitionContext completeTransition:YES];
        }];
    }

    NSArray *fromViews = [self visibleCellsForViewController:fromVC];
    NSArray *toViews = [self visibleCellsForViewController:toVC];

    __block NSArray *currentViews;
    __block NSUInteger currentVisibleViewsCount;

    void (^cellAnimation)(id, NSUInteger, BOOL*) = ^(UIView *view, NSUInteger idx, BOOL *stop){
        BOOL fromMode = currentViews == fromViews;
        NSTimeInterval delay = ((float)idx / (float)currentVisibleViewsCount) * self.maxDelay;
        if (!fromMode) {
            [view setTransform:CGAffineTransformMakeTranslation(delta, 0)];
        }
        void (^animation)() = ^{
            if (fromMode) {
                view.transform = CGAffineTransformMakeTranslation(-delta, 0);
                view.alpha = 0;
            } else {
                view.transform = CGAffineTransformIdentity;
                view.alpha = 1;
            }
        };
        void (^completion)(BOOL) = ^(BOOL finished2){
            if (fromMode) {
                [view setTransform:CGAffineTransformIdentity];
            }
        };
        if (self.transitionType == AMWaveTransitionTypeSubtle) {
            [UIView animateWithDuration:self.duration delay:delay options:UIViewAnimationOptionCurveEaseIn animations:animation completion:completion];
        } else if (self.transitionType == AMWaveTransitionTypeNervous) {
            [UIView animateWithDuration:self.duration delay:delay usingSpringWithDamping:0.75 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseIn animations:animation completion:completion];
        } else if (self.transitionType == AMWaveTransitionTypeBounce){
            [UIView animateWithDuration:self.duration delay:delay options:UIViewAnimationOptionCurveEaseInOut animations:animation completion:completion];
        }
    };


    currentViews = fromViews;
    NSArray *viewsArrays = @[fromViews, toViews];

    for (currentViews in viewsArrays) {
        // Animates all views
        currentVisibleViewsCount = currentViews.count;
        [currentViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:cellAnimation];
    }
}

- (NSArray *)visibleCellsForViewController:(UIViewController*)viewController {
    NSArray *visibleCells = nil;

    if ([viewController respondsToSelector:@selector(visibleCells)]) {
        visibleCells = ((UIViewController<AMWaveTransitioning>*)viewController).visibleCells;
    } else if ([viewController respondsToSelector:@selector(tableView)]) {
        visibleCells = ((UITableViewController*)viewController).tableView.am_visibleViews;
    }
    if (visibleCells.count) {
        return visibleCells;
    } else if (viewController.view) {
        return @[viewController.view];
    }
    return nil;
}

@end

@implementation UITableView (AMWaveTransition)

- (NSArray*)am_visibleViews {
    NSMutableArray *views = [NSMutableArray array];
    
    if (self.tableHeaderView.frame.size.height) {
        [views addObject:self.tableHeaderView];
    }
    
    NSInteger section = -1;
    for (NSIndexPath *indexPath in self.indexPathsForVisibleRows) {
        if (section != indexPath.section) {
            section = indexPath.section;
            UIView *view = [self headerViewForSection:section];
            if (view.frame.size.height) {
                [views addObject:view];
            }
            
            for (NSIndexPath *sectionIndexPath in self.indexPathsForVisibleRows) {
                if (sectionIndexPath.section != indexPath.section) {
                    continue;
                }
                
                view = [self cellForRowAtIndexPath:sectionIndexPath];
                if (view.frame.size.height) {
                    [views addObject:view];
                }
            }
            
            view = [self footerViewForSection:section];
            if (view.frame.size.height) {
                [views addObject:view];
            }
        }
    }
    
    if (self.tableFooterView.frame.size.height) {
        [views addObject:self.tableFooterView];
    }
    
    return views;
}

@end

3、AMWaveViewController.h

//
//  AMWaveTransitioning.m
//  AMWaveTransitioning
//
//  Created by Andrea on 11/04/14.
//  Copyright (c) 2015 Fancy Pixel. All rights reserved.
//

#import "AMWaveTransition.h"

typedef NS_ENUM(NSInteger, AMWaveTransitionViewControllers) {
    AMWaveTransitionToVC,
    AMWaveTransitionFromVC,
};

@interface AMWaveTransition ()

@property (nonatomic, strong) UIGestureRecognizer *gesture;
@property (nonatomic, weak) UINavigationController *navigationController;
@property (nonatomic, assign) int selectionIndexFrom;
@property (nonatomic, assign) int selectionIndexTo;
@property (nonatomic, assign) CGPoint firstTouch;

@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) NSMutableArray *attachmentsFrom;
@property (nonatomic, strong) NSMutableArray *attachmentsTo;

@end


@interface UITableView (AMWaveTransition)
- (NSArray*)am_visibleViews;
@end


@implementation AMWaveTransition

#define SCREEN_WIDTH ((([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) || ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)) ? [[UIScreen mainScreen] bounds].size.width : [[UIScreen mainScreen] bounds].size.height)

const CGFloat DURATION = 0.65;
const CGFloat MAX_DELAY = 0.15;

- (void)dealloc {
    [self detachInteractiveGesture];
}

- (instancetype)init {
    if ((self = [super init])) {
        [self setup];
        _operation = UINavigationControllerOperationNone;
        _transitionType = AMWaveTransitionTypeNervous;
    }
    return self;
}

+ (instancetype)transitionWithOperation:(UINavigationControllerOperation)operation {
    return [[self alloc] initWithOperation:operation andTransitionType:AMWaveTransitionTypeNervous];
}

- (instancetype)initWithOperation:(UINavigationControllerOperation)operation {
    return [self initWithOperation:operation andTransitionType:AMWaveTransitionTypeNervous];
}

+ (instancetype)transitionWithOperation:(UINavigationControllerOperation)operation andTransitionType:(AMWaveTransitionType)type {
    return [[self alloc] initWithOperation:operation andTransitionType:type];
}

- (instancetype)initWithOperation:(UINavigationControllerOperation)operation andTransitionType:(AMWaveTransitionType)type {
    self = [super init];
    if (self) {
        [self setup];
        _operation = operation;
        _transitionType = type;
    }
    return self;
}

- (void)setup {
    _viewControllersInset = 20;
    _interactiveTransitionType = AMWaveTransitionEdgePan;
    _animateAlphaWithInteractiveTransition = NO;
    _duration = DURATION;
    _maxDelay = MAX_DELAY;
}

- (void)attachInteractiveGestureToNavigationController:(UINavigationController *)navigationController {
    self.navigationController = navigationController;
    if (self.interactiveTransitionType == AMWaveTransitionEdgePan) {
        UIScreenEdgePanGestureRecognizer *recognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self
                                                                                                         action:@selector(handlePan:)];
        [recognizer setEdges:UIRectEdgeLeft];
        self.gesture = recognizer;
    } else {
        UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                                     action:@selector(handlePan:)];
        self.gesture = recognizer;
    }
    [navigationController.view addGestureRecognizer:self.gesture];
    self.animator = [[UIDynamicAnimator alloc]initWithReferenceView:navigationController.view];
    self.attachmentsFrom = [@[] mutableCopy];
    self.attachmentsTo = [@[] mutableCopy];
}

- (void)detachInteractiveGesture {
    UINavigationController *navigationController = self.navigationController;
    [navigationController.view removeGestureRecognizer:self.gesture];
    self.navigationController = nil;
    self.gesture = nil;
    [self.animator removeAllBehaviors];
    self.animator = nil;
}

- (void)handlePan:(UIScreenEdgePanGestureRecognizer *)gesture {
    UINavigationController *navigationController = self.navigationController; // support CLANG_WARN_OBJC_RECEIVER_WEAK
    
    // Starting controller
    UIViewController *fromVC = navigationController.topViewController;
    
    // Controller that will be visible after the pop
    UIViewController<AMWaveTransitioning> *toVC;
    NSInteger index = [navigationController.viewControllers indexOfObject:navigationController.topViewController];
    // The gesture velocity will also determine the velocity of the cells
    float velocity = [gesture velocityInView:navigationController.view].x;
    CGPoint touch = [gesture locationInView:navigationController.view];
    if (index == 0) {
        // Simple attach animation
        touch.x = 0;
        toVC = nil;
    } else if (index != NSNotFound) {
        toVC = (UIViewController<AMWaveTransitioning> *)navigationController.viewControllers[index-1];
    }
    
    NSArray *fromViews = [self visibleCellsForViewController:fromVC];
    NSArray *toViews = [self visibleCellsForViewController:toVC];
    
    if (gesture.state == UIGestureRecognizerStateBegan) {
        if (self.interactiveTransitionType == AMWaveTransitionFullScreenPan) {
            self.firstTouch = touch;
        } else {
            self.firstTouch = CGPointZero;
        }
        [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            // The 'selected' cell will be the one leading the other cells
            if (CGRectContainsPoint([view.superview convertRect:view.frame toView:nil], touch)) {
                self.selectionIndexFrom = (int)idx;
            }
            [self createAttachmentForView:view inVC:AMWaveTransitionFromVC];
        }];
        
        
        // Kick the 'new' cells outside the view
        [navigationController.view insertSubview:toVC.view belowSubview:navigationController.navigationBar];
        toViews = [self visibleCellsForViewController:toVC]; // re-read, because toVC might have been not ready before
        [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            [self kickCellOutside:view];
        }];
        
        [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            CGRect futureRect = view.frame;
            futureRect.origin.x = 0;
            if (CGRectContainsPoint([view.superview convertRect:futureRect toView:nil], touch)) {
                self.selectionIndexTo = (int)idx;
            }
            [self createAttachmentForView:view inVC:AMWaveTransitionToVC];
        }];
        
    } else if (gesture.state == UIGestureRecognizerStateChanged) {
        
        [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            [self changeAttachmentWithIndex:idx
                                     inView:view
                                     touchX:touch.x
                                   velocity:velocity
                                       inVC:AMWaveTransitionFromVC];
        }];
        
        [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
            [self changeAttachmentWithIndex:idx
                                     inView:view
                                     touchX:touch.x
                                   velocity:velocity
                                       inVC:AMWaveTransitionToVC];
        }];
        
    } else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) {
        [self.attachmentsFrom enumerateObjectsUsingBlock:^(UIAttachmentBehavior *obj, NSUInteger idx, BOOL *stop) {
            [self.animator removeBehavior:obj];
        }];
        [self.attachmentsFrom removeAllObjects];
        
        [self.attachmentsTo enumerateObjectsUsingBlock:^(UIAttachmentBehavior *obj, NSUInteger idx, BOOL *stop) {
            [self.animator removeBehavior:obj];
        }];
        [self.attachmentsTo removeAllObjects];
        
        if (gesture.state == UIGestureRecognizerStateEnded && velocity > 0) {
            // Complete the transition
            [UIView animateWithDuration:0.3 animations:^{
                [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self completeFromVC:view];
                }];
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self setPresentedFrameForView:view];
                }];
            } completion:^(BOOL finished) {
                toVC.view.backgroundColor = fromVC.view.backgroundColor;
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self animationCompletionForInteractiveTransitionForView:view];
                }];
                
                [navigationController popViewControllerAnimated:NO];
            }];
        } else {
            // Abort
            [UIView animateWithDuration:0.3 animations:^{
                [fromViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self setPresentedFrameForView:view];
                }];
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self completeToVC:view];
                }];
            } completion:^(BOOL finished) {
                // Bring 'silently' the cell back to their place, or the normal pop operation would fail
                [toViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
                    [self animationCompletionForInteractiveTransitionForView:view];
                }];
                [toVC.view removeFromSuperview];
            }];
            
        }
    }
}

- (void)animationCompletionForInteractiveTransitionForView:(UIView *)view {
    CGRect rect = view.frame;
    rect.origin.x = 0;
    UINavigationController *navigationController = self.navigationController;
    if (navigationController.navigationBar.translucent && !navigationController.navigationBar.hidden) {
        rect.origin.y -= navigationController.navigationBar.frame.origin.y + navigationController.navigationBar.frame.size.height;
    } else {
        rect.origin.y -= [[UIApplication sharedApplication] statusBarFrame].size.height;
    }
    view.frame = rect;
    view.alpha = [self alphaForView:view];
}

- (void)setPresentedFrameForView:(UIView *)view {
    CGRect rect = view.frame;
    rect.origin.x = 0;
    view.frame = rect;
    view.alpha = [self alphaForView:view];
}

- (void)kickCellOutside:(UIView *)view {
    CGRect rect = view.frame;
    rect.origin.x = -SCREEN_WIDTH - self.viewControllersInset;
    UINavigationController *navigationController = self.navigationController;
    if (navigationController.navigationBar.translucent && !navigationController.navigationBar.hidden) {
        rect.origin.y += navigationController.navigationBar.frame.origin.y + navigationController.navigationBar.frame.size.height;
    } else {
        rect.origin.y += [[UIApplication sharedApplication] statusBarFrame].size.height;
    }
    view.alpha = [self alphaForView:view];
    view.frame = rect;
}

- (void)completeToVC:(UIView *)view {
    [self completeTransitionWithView:view inVC:AMWaveTransitionToVC];
}

- (void)completeFromVC:(UIView *)view {
    [self completeTransitionWithView:view inVC:AMWaveTransitionFromVC];
}

- (void)completeTransitionWithView:(UIView *)view inVC:(AMWaveTransitionViewControllers)viewController {
    CGRect rect = view.frame;
    if (viewController == AMWaveTransitionFromVC) {
        rect.origin.x = SCREEN_WIDTH - self.viewControllersInset;
    } else {
        rect.origin.x = -SCREEN_WIDTH - self.viewControllersInset;
    }
    view.frame = rect;
    view.alpha = [self alphaForView:view];
}

- (void)changeAttachmentWithIndex:(NSUInteger)index
                           inView:(UIView *)view
                           touchX:(CGFloat)touchX
                         velocity:(CGFloat)velocity
                             inVC:(AMWaveTransitionViewControllers)viewController {
    int selectionIndex;
    NSInteger correction = 2;
    NSMutableArray *arrayWithAttachments;
    if (viewController == AMWaveTransitionToVC) {
        arrayWithAttachments = self.attachmentsTo;
        selectionIndex = self.selectionIndexTo;
    } else {
        arrayWithAttachments = self.attachmentsFrom;
        selectionIndex = self.selectionIndexFrom;
        correction = -correction;
    }
    
    float delta = touchX - self.firstTouch.x - abs(selectionIndex - (int)index) * velocity / 50;
    
    // Prevent the anchor point from going 'over' the cell
    if (delta > view.frame.origin.x + view.frame.size.width / 2 && viewController == AMWaveTransitionFromVC) {
        delta = view.frame.origin.x + view.frame.size.width / 2 + correction;
    } else if (delta < view.frame.origin.x + view.frame.size.width / 2 && viewController == AMWaveTransitionToVC) {
        delta = view.frame.origin.x + view.frame.size.width / 2 + correction;
    }
    view.alpha = [self alphaForView:view];
    [arrayWithAttachments[index] setAnchorPoint:(CGPoint){delta, [view.superview convertPoint:view.frame.origin toView:nil].y + view.frame.size.height / 2}];
}

- (void)createAttachmentForView:(UIView *)view inVC:(AMWaveTransitionViewControllers)viewController {
    UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:view attachedToAnchor:(CGPoint){0, [view.superview convertPoint:view.frame.origin toView:nil].y + view.frame.size.height / 2}];
    [attachment setDamping:0.4];
    [attachment setFrequency:1];
    [self.animator addBehavior:attachment];
    view.alpha = [self alphaForView:view];
    
    NSMutableArray *arrayWithAttachments;
    if (viewController == AMWaveTransitionToVC) {
        arrayWithAttachments = self.attachmentsTo;
    } else {
        arrayWithAttachments = self.attachmentsFrom;
    }
    
    [arrayWithAttachments addObject:attachment];
}

- (CGFloat)alphaForView:(UIView *)view {
    if (self.animateAlphaWithInteractiveTransition) {
        CGFloat width = SCREEN_WIDTH - self.viewControllersInset;
        CGFloat alpha = (width - fabs(view.frame.origin.x)) * (1 / width);
        return alpha;
    } else {
        return 1.0;
    }
}

#pragma mark - Non interactive transition

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return self.duration + self.maxDelay;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    UIViewController *fromVC;
    if ([[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] isKindOfClass:[UINavigationController class]]) {
        fromVC = (UIViewController*)([(UINavigationController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] visibleViewController]);
    } else {
        fromVC = (UIViewController*)([transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]);
    }
    
    UIViewController *toVC;
    if ([[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] isKindOfClass:[UINavigationController class]]) {
        toVC = (UIViewController*)([(UINavigationController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] visibleViewController]);
    } else {
        toVC = (UIViewController*)([transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]);
    }
    
    [[transitionContext containerView] addSubview:toVC.view];
    
    CGFloat delta;
    if (self.operation == UINavigationControllerOperationPush) {
        delta = SCREEN_WIDTH + self.viewControllersInset;
    } else {
        delta = -SCREEN_WIDTH - self.viewControllersInset;
    }
    
    // Move the destination in place
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    // And kick it aside
    toVC.view.transform = CGAffineTransformMakeTranslation(delta, 0);

    [transitionContext containerView].backgroundColor = fromVC.view.backgroundColor;

    // Trigger the layout of the new cells
    [[transitionContext containerView] layoutIfNeeded];

    // Plain animation that moves the destination controller in place. Once it's done it will notify the transition context
    if (self.operation == UINavigationControllerOperationPush) {
        [toVC.view setTransform:CGAffineTransformMakeTranslation(1, 0)];
        [UIView animateWithDuration:self.duration + self.maxDelay delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [toVC.view setTransform:CGAffineTransformIdentity];
        } completion:^(BOOL finished2) {
            [transitionContext completeTransition:YES];
        }];
    } else {
        [fromVC.view setTransform:CGAffineTransformMakeTranslation(1, 0)];
        [toVC.view setTransform:CGAffineTransformIdentity];
        [UIView animateWithDuration:self.duration + self.maxDelay delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [fromVC.view setTransform:CGAffineTransformMakeTranslation(0, 0)];
        } completion:^(BOOL finished2) {
            [fromVC.view removeFromSuperview];
            [transitionContext completeTransition:YES];
        }];
    }

    NSArray *fromViews = [self visibleCellsForViewController:fromVC];
    NSArray *toViews = [self visibleCellsForViewController:toVC];

    __block NSArray *currentViews;
    __block NSUInteger currentVisibleViewsCount;

    void (^cellAnimation)(id, NSUInteger, BOOL*) = ^(UIView *view, NSUInteger idx, BOOL *stop){
        BOOL fromMode = currentViews == fromViews;
        NSTimeInterval delay = ((float)idx / (float)currentVisibleViewsCount) * self.maxDelay;
        if (!fromMode) {
            [view setTransform:CGAffineTransformMakeTranslation(delta, 0)];
        }
        void (^animation)() = ^{
            if (fromMode) {
                view.transform = CGAffineTransformMakeTranslation(-delta, 0);
                view.alpha = 0;
            } else {
                view.transform = CGAffineTransformIdentity;
                view.alpha = 1;
            }
        };
        void (^completion)(BOOL) = ^(BOOL finished2){
            if (fromMode) {
                [view setTransform:CGAffineTransformIdentity];
            }
        };
        if (self.transitionType == AMWaveTransitionTypeSubtle) {
            [UIView animateWithDuration:self.duration delay:delay options:UIViewAnimationOptionCurveEaseIn animations:animation completion:completion];
        } else if (self.transitionType == AMWaveTransitionTypeNervous) {
            [UIView animateWithDuration:self.duration delay:delay usingSpringWithDamping:0.75 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseIn animations:animation completion:completion];
        } else if (self.transitionType == AMWaveTransitionTypeBounce){
            [UIView animateWithDuration:self.duration delay:delay options:UIViewAnimationOptionCurveEaseInOut animations:animation completion:completion];
        }
    };


    currentViews = fromViews;
    NSArray *viewsArrays = @[fromViews, toViews];

    for (currentViews in viewsArrays) {
        // Animates all views
        currentVisibleViewsCount = currentViews.count;
        [currentViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:cellAnimation];
    }
}

- (NSArray *)visibleCellsForViewController:(UIViewController*)viewController {
    NSArray *visibleCells = nil;

    if ([viewController respondsToSelector:@selector(visibleCells)]) {
        visibleCells = ((UIViewController<AMWaveTransitioning>*)viewController).visibleCells;
    } else if ([viewController respondsToSelector:@selector(tableView)]) {
        visibleCells = ((UITableViewController*)viewController).tableView.am_visibleViews;
    }
    if (visibleCells.count) {
        return visibleCells;
    } else if (viewController.view) {
        return @[viewController.view];
    }
    return nil;
}

@end

@implementation UITableView (AMWaveTransition)

- (NSArray*)am_visibleViews {
    NSMutableArray *views = [NSMutableArray array];
    
    if (self.tableHeaderView.frame.size.height) {
        [views addObject:self.tableHeaderView];
    }
    
    NSInteger section = -1;
    for (NSIndexPath *indexPath in self.indexPathsForVisibleRows) {
        if (section != indexPath.section) {
            section = indexPath.section;
            UIView *view = [self headerViewForSection:section];
            if (view.frame.size.height) {
                [views addObject:view];
            }
            
            for (NSIndexPath *sectionIndexPath in self.indexPathsForVisibleRows) {
                if (sectionIndexPath.section != indexPath.section) {
                    continue;
                }
                
                view = [self cellForRowAtIndexPath:sectionIndexPath];
                if (view.frame.size.height) {
                    [views addObject:view];
                }
            }
            
            view = [self footerViewForSection:section];
            if (view.frame.size.height) {
                [views addObject:view];
            }
        }
    }
    
    if (self.tableFooterView.frame.size.height) {
        [views addObject:self.tableFooterView];
    }
    
    return views;
}

@end

4、AMWaveViewController.m

//
//  AMWaveViewController.m
//  Demo
//
//  Created by Andrea Mazzini on 16/04/14.
//  Copyright (c) 2015 Fancy Pixel. All rights reserved.
//

#import "AMWaveViewController.h"

@interface AMWaveViewController ()

@end

@implementation AMWaveViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.navigationController setDelegate:self];
    [self.interactive attachInteractiveGestureToNavigationController:self.navigationController];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.interactive detachInteractiveGesture];
}

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController*)fromVC
                                                 toViewController:(UIViewController*)toVC {
    if (operation != UINavigationControllerOperationNone) {
        return [AMWaveTransition transitionWithOperation:operation andTransitionType:AMWaveTransitionTypeNervous];
    }
    return nil;
}

- (NSArray *)visibleCells {
    return nil;
}

- (void)dealloc {
    [self.navigationController setDelegate:nil];
}

@end
 



 

以上是全部有效代码,这两个类可以直接用。效果嘛不错,自己试试哦!

2016-02-28 

posted on 2016-02-28 21:19  鹏哥的技术博客  阅读(440)  评论(0)    收藏  举报