有料的自定义转场动画

一、前言

这篇文章早在半个月前就已经打算写了,由于打算来杭州工作,从一个地区到另一个地区,再加上找工作,准备面试等,所以折腾到现在。在正式入职之前,利用最后的空闲时间,将这篇技术文章完善一下。

这篇文章的价值所在:

  • 熟练掌握Modal,Push, tab的自定义转场一整套流程
  • 手势驱动Modal,Push,tab的自定义转场
  • 使用UIView、CAAnimation动画实现转场动画
  • 特殊情况下使用消息转发机制完成两个target对同一个方法消息的转发

二、 Demo效果

转场演示.gif

三、 转场协议

1. 大话转场协议

说起控制器的转场,除了向当前控制器添加子控制器,并将子控制器的view添加到当前控制器的view上,我们平常主要使用的有以下三种转场情况:

  • UIViewControllerModal

这种转场,系统提供给我们的转场协议为UIViewControllerTransitioningDelegate,其涉及到的转场协议方法有如下三个:

// 返回一个present转场动画协议,让转场时执行动画协议里实现的present动画
- (id)
animationControllerForPresentedController:(UIViewController *)presented 
presentingController:(UIViewController *)presenting 
sourceController:(UIViewController *)source;

// 返回一个dismiss转场动画协议,让转场时执行动画协议里实现的dismiss动画
- (id)
animationControllerForDismissedController:(UIViewController *)dismissed;

// 返回一个dismiss手势过渡对象,手势驱动dismiss转场
- (nullable id )
interactionControllerForDismissal:(id )animator
  • UINavigationControllerPush

这种转场,系统提供的转场协议为UINavigationControllerDelegate,其涉及到转场协议方法有如下两个:

// 返回转场动画协议,让转场时执行动画协议里实现的方法。我们根
// 据operation参数可以区分是push还是pop操作
- (nullable id )
navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC;

// 返回转场手势过渡对象
- (nullable id )
navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id ) animationController;
  • UITabbarControllerSelect

这种转场,系统提供的转场协议为UITabBarControllerDelegate,其涉及到转场协议方法有如下两个:

// 返回转场动画协议,让转场时执行动画协议里实现的方法
- (nullable id )
tabBarController:(UITabBarController *)tabBarController
animationControllerForTransitionFromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC;

// 返回转场手势驱动对象
- (nullable id )
tabBarController:(UITabBarController *)tabBarController
interactionControllerForAnimationController: (id )animationController;
2.本组件中的转场控制类

本组件通过XLLTransferDelegate这个类来统一管理Modal、Push转场协议。具体的可以到Demo中去看。为什么tab转场不在这个类掌控呢?别着急,下面会给您分析
XLLTransferDelegate的唯一初始化方法如下:

/**
 初始化方法

 @param animationStyle 动画类型 (目前只提供三种动画类型,两个UIView动画,一个核心动画)
 @param interactiveStyle 手势转场类型
 @return 实例对象
 */
- (instancetype)initWithAnimationStyle:(XLLAnimationStyle)animationStyle 
InteractiveStyle:(XLLInteractiveStyle)interactiveStyle;

/**
 手势驱动转场类型
 */
typedef NS_ENUM(NSInteger, XLLInteractiveStyle) {
    
    XLLTransferInteractiveShow = 1000, //push pop
    XLLTransferInteractiveModal,       //present dismiss
    XLLTransferInteractiveTabSelect    //tab选择
};

/**
 转场动画类型
 */
typedef NS_ENUM(NSInteger, XLLAnimationStyle) {
    
    XLLAnimationStyleDoor = 1000, //开门动画
    XLLAnimationStyleCircle,      //圆形扩展动画
    XLLAnimationStyleKuGou,       //酷狗音乐那种动画
    XLLAnimationStyleTab          //tab样式
};

二、转场动画协议

1.协议方法

UIViewControllerAnimatedTransitioning是转场动画的协议。其有两个协议方法,如下:

// 设置转场动画时间
- (NSTimeInterval)transitionDuration:(nullable id )transitionContext;
// 设置转场动画逻辑
- (void)animateTransition:(id )transitionContext;
2.转场上下文transtionContext

通过转场上下文可以获取到转场前后的控制器与前后view。由此设计出自己想要的转场动画。

3.本组件中的动画协议类

XLLTransferAnimation为本组件中的动画协议类。初始化的时候,通过传入不同的动画枚举来执行不同的转场动画。具体的可以到Demo中去看。

/**
 初始化方法

 @param animationStyle 动画类型
 @return 实例对象
 */
- (instancetype)initWithAnimationStyle:(XLLAnimationStyle)animationStyle;

三、 手势驱动

1.大话手势驱动

关于转场的手势驱动,系统给我们提供了一个继承了手势驱动协议UIViewControllerInteractiveTransitioning的一个类UIPercentDrivenInteractiveTransition
我们通过在控制器视图上植入手势,并在手势代理的各个阶段,使用手势驱动类的方法控制转场进度,从而达到手势驱动转场的目的。

2.本组件中的手势驱动类

XLLTransferInteractive为本组件中的手势驱动类。其直接继承于** UIPercentDrivenInteractiveTransition**,并且暴露给使用者植入手势的方法。初始化的时候,传入不同的转场类型。具体的可以到Demo中去看。

/**
 初始化手势转场实例

 @param interactiveStyle 手势转场类型
 @return 实例
 */
- (instancetype)initWithInteractiveStyle:(XLLInteractiveStyle)interactiveStyle;

四、 细说UITabbarController自定义转场实现思想

1. 起因

如果你有仔细看上面的内容,会知道我们通过XLLTransferDelegate这个类来管理转场协议。实现Modal,Push转场的掌控。但是tabbar的转场却没有在掌控在这个类中。
原因如下:

tabbar转场协议为UITabbarControllerDelegate,这个协议在项目中是经常遇到的。比如说在点击tabbar的时候,弹出未登录的提示等等,这个时候就需要用到这个协议里的一些方法。所以我不能很自私地为了实现自己的转场,而将UITabbarControllerDelegate的使用权独自揽入怀中。

2.解决思想

使用消息转发,将UITabbarControllerDelegate里的协议方法转发到两个不同target中。

3.具体代码实现
  • 在组件中,我们创建一个UITabbarController的分类文件UITabBarController+XLLTransfer
  • 重写load方法,利用runtimesetDelegate与自定义的setMyDelegate进行方法交换。
  • 在拦截下来的setMyDelegate中,我们将UITabbarController的代理设置为一个XLLTabbarControllerDelegate对象。
  • XLLTabbarControllerDelegate对象有两个属性,insideDelegateoutsideDelegate。我们用insideDelegate设为分类本身,用outsideDelegate设为用户设定的代理对象。
//编译时方法交换
+ (void)load
{
    Method originMethod = class_getInstanceMethod(self, @selector(setDelegate:));
    Method myMethod = class_getInstanceMethod(self, @selector(setMyDelegate:));
    method_exchangeImplementations(originMethod, myMethod);
}

- (void)setMyDelegate:(id )delegate
{
    XLLTabbarControllerDelegate *myDelegate = [[XLLTabbarControllerDelegate alloc] init];
    myDelegate.insideDelegate = self;
    myDelegate.outsideDelegate = delegate;
    [self setMyDelegate:myDelegate];
    objc_setAssociatedObject(self, myDelegate_Key, myDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //生成手势
    [self.transferInteractive addGestureOnVC:self];
}
  • 接下来我们要做的就是将协议方法分发到两个属性target中,让这两个target去实现协议方法。
#pragma mark - lazy loading
- (Protocol *)protocol
{
    if (_protocol == nil)
    {
        _protocol = objc_getProtocol("UITabBarControllerDelegate");
    }
    return _protocol;
}

//重写响应方法
- (BOOL)respondsToSelector:(SEL)aSelector
{
    struct objc_method_description des = protocol_getMethodDescription(self.protocol, aSelector, NO, YES);
    if (des.types == NULL)
    {
        //1.如果不是此协议内的SEL,走系统判定
        return [super respondsToSelector:aSelector];
    }
    //2.协议内SEL是否响应,由内外部代理是否实现了说的算
    if ([self.insideDelegate respondsToSelector:aSelector] || [self.outsideDelegate respondsToSelector:aSelector]) {
        return YES;
    }
    //3.内外部如果没有实现协议内SEL,系统来判定
    return [super respondsToSelector:aSelector];
}

//消息转发,让真正的内外部代理实现方法
//获取方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *insideSign = [self.insideDelegate methodSignatureForSelector:aSelector];
    NSMethodSignature *outsideSign = [self.outsideDelegate methodSignatureForSelector:aSelector];
    return insideSign?:outsideSign?:nil;
}

//指定消息转发target
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector = anInvocation.selector;
    //1.以内部代理为主,如果内部代理响应,则让内部代理先执行
    BOOL isResponse = NO;
    if ([self.insideDelegate respondsToSelector:selector])
    {
        [anInvocation invokeWithTarget:self.insideDelegate];
        isResponse = YES;
    }
    if ([self.outsideDelegate respondsToSelector:selector])
    {
        [anInvocation invokeWithTarget:self.outsideDelegate];
        isResponse = YES;
    }
    //2.如果内外部都没实现此方法,却调用了,抛出unRecognize..经典报错
    if (!isResponse)
    {
        [self doesNotRecognizeSelector:selector];
    }
}

这样我们在分类中实现UITabbarControllerDelegate中的转场协议方法,同时也不影响用户设定其他的对象来实现UITabbarControllerDelegate的其他协议方法满足项目需求。

四、后语

以上介绍的也比较笼统,结合Demo查看,会更快地掌握我要表达的意思与自定义转场的用法。这是Demo地址,有问题欢迎指正,共同进步。

你可能感兴趣的:(有料的自定义转场动画)