美文网首页ui问题iOS 开发iOS UI相关
iOS动画 — 转场 — TabBar滑动

iOS动画 — 转场 — TabBar滑动

作者: 天空中的球 | 来源:发表于2016-08-14 23:55 被阅读1397次

    之前有涉及到 CATransition View 的转场,但实际上可能我们用到 VC 的转场可能更多一些。此时我先从一个需求出发举例,就是 ** tabBar 增加滑动屏幕切换 item 的效果**。

    TabBar 滑动
    • 1、动画交互 —— UIViewControllerAnimatedTransitioning
    • 2、什么时候需要动画交互 —— UITabBarControllerDelegate
    • 3、真正执行的地方 —— UITabBarController

    1、动画交互 —— UIViewControllerAnimatedTransitioning

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    typedef NS_ENUM(NSInteger,TabOperationDirection) {
        TabLeftDirection,
        TabRightDirection
    };
    
    @interface ScrollTabBarAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    
    @property (nonatomic, assign) TabOperationDirection tabScrollDirection;
    
    @end
    
    #import "ScrollTabBarAnimator.h"
    
    @implementation ScrollTabBarAnimator
    
    //动画持续时间
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
        return 0.3;
    }
    
    //动画执行效果
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
        
        // 获取 toView fromView
        UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIView *containerView = [transitionContext containerView];
        if (!toViewController || !fromViewController || !containerView) return;
        
        // 给 toView fromView 设定相应的值
        toViewController.view.transform = CGAffineTransformIdentity;
        fromViewController.view.transform = CGAffineTransformIdentity;
        CGFloat translation = containerView.frame.size.width;
    
        switch (self.tabScrollDirection) {
            case TabLeftDirection:
                translation = translation;
                break;
            case TabRightDirection:
                translation = -translation;
                break;
            default:
                break;
        }
    
        [containerView addSubview:toViewController.view];
        toViewController.view.transform = CGAffineTransformMakeTranslation(-translation, 0);
        // 真正的变化
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromViewController.view.transform = CGAffineTransformMakeTranslation(translation, 0);
            toViewController.view.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            fromViewController.view.transform = CGAffineTransformIdentity;
            toViewController.view.transform = CGAffineTransformIdentity;
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }
    
    @end
    
    

    2、什么时候需要动画交互 —— UITabBarControllerDelegate

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface ScrollTabBarDelegate : NSObject <UITabBarControllerDelegate>
    
    @property (nonatomic, assign) BOOL interactive;
    @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
    
    @end
    
    #import "ScrollTabBarDelegate.h"
    #import "ScrollTabBarAnimator.h"
    
    
    @interface ScrollTabBarDelegate ()
    
    @property (nonatomic, strong) ScrollTabBarAnimator *tabBarAnimator;
    
    @end
    
    @implementation ScrollTabBarDelegate
    
    - (instancetype)init {
        if (self = [super init]) {
            _interactive = NO;
            _interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
            _tabBarAnimator = [[ScrollTabBarAnimator alloc] init];
    
        }
        return self;
    }
    
    - (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                                   interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController {
        return self.interactive ? self.interactionController : nil;
    }
    
    - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
                         animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC {
        
        NSInteger fromIndex = [tabBarController.viewControllers indexOfObject:fromVC];
        NSInteger toIndex = [tabBarController.viewControllers indexOfObject:toVC];
        self.tabBarAnimator.tabScrollDirection = (toIndex < fromIndex) ? TabLeftDirection: TabRightDirection;
        return self.tabBarAnimator;
    
    }
    
    @end
    
    

    3、真正执行的地方 —— UITabBarController

    #import "ScrollTabBarController.h"
    #import "ScrollTabBarDelegate.h"
    
    @interface ScrollTabBarController ()
    
    @property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
    @property (nonatomic, assign) NSInteger subViewControllerCount;
    @property (nonatomic, strong) ScrollTabBarDelegate *tabBarDelegate;
    
    @end
    
    @implementation ScrollTabBarController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 正确的给予 count
        self.subViewControllerCount = self.viewControllers ? self.viewControllers.count : 0;
        // 代理
        self.tabBarDelegate = [[ScrollTabBarDelegate alloc] init];
        self.delegate = self.tabBarDelegate;
        // 增加滑动手势
        self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandle:)];
        [self.view addGestureRecognizer:self.panGesture];
    
    }
    
    - (void)panHandle:(UIPanGestureRecognizer *)panGesture {
        // 获取滑动点
        CGFloat translationX = [panGesture translationInView:self.view].x;
        CGFloat progress = fabs(translationX)/self.view.frame.size.width;
        
        switch (panGesture.state) {
            case UIGestureRecognizerStateBegan:
            {
                self.tabBarDelegate.interactive = YES;
                CGFloat velocityX = [panGesture velocityInView:self.view].x;
                if (velocityX < 0) {
                    if (self.selectedIndex < self.subViewControllerCount - 1) {
                        self.selectedIndex += 1;
                    }
                }
                else {
                    if (self.selectedIndex > 0) {
                        self.selectedIndex -= 1;
                    }
                }
            }
                break;
            case UIGestureRecognizerStateChanged:
            {
                [self.tabBarDelegate.interactionController updateInteractiveTransition:progress];
            }
                
                break;
            case UIGestureRecognizerStateEnded:
            case UIGestureRecognizerStateFailed:
            case UIGestureRecognizerStateCancelled:
            {
                if (progress > 0.3) {
                    self.tabBarDelegate.interactionController.completionSpeed = 0.99;
                    [self.tabBarDelegate.interactionController finishInteractiveTransition];
                }else{
                    //转场取消后,UITabBarController 自动恢复了 selectedIndex 的值,不需要我们手动恢复。
                    self.tabBarDelegate.interactionController.completionSpeed = 0.99;
                    [self.tabBarDelegate.interactionController cancelInteractiveTransition];
                }
                self.tabBarDelegate.interactive = NO;
            }
                break;
            default:
                break;
        }
    }
    
    @end
    
    

    大致效果就出来,还有点瑕疵,大致对自定义转场的感受分为:

    • 第一步知道我们要什么效果,切换中应该发生什么?。
    //返回动画的时间
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext;
    //切换时的UIView的设置和动画
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; 
    
    • 第二步,这个效果在什么地方实现。
    //UINavigationController 的 delegate 属性遵守该协议。
    <UINavigationControllerDelegate> 
    //UITabBarController 的 delegate 属性遵守该协议。
    <UITabBarControllerDelegate> 
    //UIViewController 的 transitioningDelegate 属性遵守该协议。
    <UIViewControllerTransitioningDelegate> 
    

    上述例子用的就是UITabBarControllerDelegate,但实际上我们很多时候用的是UIViewControllerTransitioningDelegate。

    • 第三步就是真正的实现,是否需要使用自定义的切换效果。
    @protocol UIViewControllerTransitioningDelegate;
    
    @interface UIViewController(UIViewControllerTransitioning)
    
    @property (nullable, nonatomic, weak) id <UIViewControllerTransitioningDelegate> transitioningDelegate NS_AVAILABLE_IOS(7_0);
    
    @end
    
    detailVC.transitioningDelegate = self;
    [self.navigationController presentViewController:detailVC animated:YES completion:nil];
    

    至于上面那个例子,增加手势滑动,是特殊一点的场景,实际上还是 transitioningDelegate 的另一种展示。

    总的说来,具体的效果还是得看第一步中的实现,目前我还没有深入,继续学习吧,决定多看看这个经典的VCTransitionsLibrary中的例子!

    PS : 上述代码已放在:ScrollTabBar,欢迎提出问题。

    备注参考:
    http://kittenyang.com/uiviewcontrollertransitioning/
    https://github.com/seedante/iOS-Note/wiki/ViewController-Transition

    相关文章

      网友评论

      • 走停2015_iOS开发:难道只有我发现了问题了吗?? 如果频繁切换的话会有问题
        b2efe7751b24:打个断点也会出问题
      • 天明天:培球,不错啊。在网上搜到你写的文章了,哈哈哈
      • 鬼谷门生:大神你好 那么要是二级页面也存在左右滑动视图怎么区分呢
        BohrIsLay:@BohrIsLay 自己研究了下,在二级界面用一个scrollView, contentSize等于分区数乘以屏幕宽,这样二级界面也可以左右滑动,但是会出现tabbarController的pan手势被二级界面的scrollView优先使用,以至于这个二级界面不能滑倒其他二级界面,解决这个问题,只需要判断contentOffset,禁用scrollView交互
        BohrIsLay:大神,你现在有思路怎么做吗,我刚好遇到这个问题
      • ilovemdcat99:学了非常不错
      • macfai:楼主这个功能实现的太赞了,正需要这个效果,好好研究一下你的代码,mark

      本文标题:iOS动画 — 转场 — TabBar滑动

      本文链接:https://www.haomeiwen.com/subject/dysjsttx.html