一、前言
这篇文章早在半个月前就已经打算写了,由于打算来杭州工作,从一个地区到另一个地区,再加上找工作,准备面试等,所以折腾到现在。在正式入职之前,利用最后的空闲时间,将这篇技术文章完善一下。
这篇文章的价值所在:
- 熟练掌握Modal,Push, tab的自定义转场一整套流程
- 手势驱动Modal,Push,tab的自定义转场
- 使用UIView、CAAnimation动画实现转场动画
- 特殊情况下使用消息转发机制完成两个target对同一个方法消息的转发
二、 Demo效果
三、 转场协议
1. 大话转场协议
说起控制器的转场,除了向当前控制器添加子控制器,并将子控制器的view添加到当前控制器的view上
,我们平常主要使用的有以下三种转场情况:
- UIViewController的Modal
这种转场,系统提供给我们的转场协议为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
- UINavigationController的Push
这种转场,系统提供的转场协议为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;
- UITabbarController的Select
这种转场,系统提供的转场协议为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方法,利用runtime将setDelegate与自定义的setMyDelegate进行方法交换。
- 在拦截下来的setMyDelegate中,我们将UITabbarController的代理设置为一个XLLTabbarControllerDelegate对象。
- XLLTabbarControllerDelegate对象有两个属性,insideDelegate和outsideDelegate。我们用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地址,有问题欢迎指正,共同进步。