上一篇已经完成了导航栏效果的渐变。但是侧滑返回的时候,导航栏从不透明界面跳转到透明界面时,总是会突变,感觉很膈应。这里将用runtime截获系统的方法来完成对导航栏动画的逆袭
由于监测侧滑手势的方法是系统管理的,并没有暴露给我们。所以我们先要做一些准备工作:
利用Runtime 获取方法、属性、成员属性。
- (NSArray *)getAllMethods {
unsigned int count = 0;
Method *list = class_copyMethodList([self class], &count);
NSMutableArray *methodArray = [NSMutableArray array];
for (int i = 0; i < count; i++) {
Method method = list[i];
SEL name = method_getName(method);
unsigned int params = method_getNumberOfArguments(method);
const char *encode = method_getTypeEncoding(method);
const char *name_c = sel_getName(name);
NSString *str = [NSString stringWithFormat:@"方法名:%s, 参数个数:%d, 参数类型: %s", name_c, params, encode];
[methodArray addObject:str];
}
free(list);
return methodArray;
}
- (NSArray *)getAllProperties
{
unsigned int count = 0;
objc_property_t *properties =class_copyPropertyList([self class], &count);
NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
objc_property_t property = properties[i];
const char* propertyName =property_getName(property);
[propertiesArray addObject: [NSString stringWithUTF8String:propertyName]];
}
free(properties);
return propertiesArray;
}
- (NSArray *)getAllIvars {
unsigned int count = 0;
NSMutableArray *ivarArray = [NSMutableArray array];
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i< count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *encode = ivar_getTypeEncoding(ivar);
NSString *str = [NSString stringWithFormat:@"%s, %s", name, encode];
[ivarArray addObject:str];
}
free(ivars);
return ivarArray;
}
保存UIViewController导航栏的alpha值:
这里有两种方法:
方法一:所有控制都继承一个RootController。在RootController里面添加alpha属性。
方法二:添加UIViewController的Category,在Category里面添加alpha属性,但是需要runtime重写set 和 get方法。
static NSString *alphaKey = @"alphaKey";
@implementation UIViewController (alpha)
@dynamic navAlpha;
- (CGFloat)navAlpha {
CGFloat alpha = [objc_getAssociatedObject(self, &alphaKey) floatValue];
return alpha;
}
- (void)setNavAlpha:(CGFloat)navAlpha {
objc_setAssociatedObject(self, &alphaKey, @(navAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self setNavigationBarAlpha:navAlpha];
}
获取UINavigationController方法列表:
NSArray *methodArr = [UINavigationController getAllMethods];
NSLog(@"%@",methodArr);
由于之前自己找UINavigationController中的方法的时候点到UIViewControllerAnimatedTransitioning这个里面翻阅了一下发现有两个协议里面都有下面几个关于 过度交互 的方法,于是互在打印的方法数组里也查下发现也有于是就尝试了一下,发现果然是这几个方法:
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
- (void)cancelInteractiveTransition;
- (void)finishInteractiveTransition;
这里参数类型 “v24@0:8d16” 这里v表示返回值类型为void,后面的d16表示参数类型为double类型,与上面方法一致。
然后我们对上面几个方法进行方法交换,系统相应该方法的时候能完成自己想做的事:
NSArray *arr = @[@"_updateInteractiveTransition:", @"_cancelInteractiveTransition:transitionContext:", @"_finishInteractiveTransition:transitionContext:"];
for (int i = 0; i < arr.count; i++) {
NSString *mySel = [NSString stringWithFormat:@"yhh%@", arr[i]];
Method sysMethod = class_getInstanceMethod(self, NSSelectorFromString(arr[i]));
Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySel));
method_exchangeImplementations(sysMethod, myMethod);
}
/*
*此方法在手指在屏幕上,侧滑返回过程中调用。
**/
- (void)yhh_updateInteractiveTransition:(CGFloat)percentComplete {
// percentComplete是当前移动距离与屏幕的比例
NSLog(@"%f", percentComplete);
UIViewController *topvc = self.topViewController;
id <UIViewControllerTransitionCoordinator> tran = topvc.transitionCoordinator;
// fromvc 从哪个控制来, tovc去哪个控制器
UIViewController *fromvc = [tran viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *tovc = [tran viewControllerForKey:UITransitionContextToViewControllerKey];
NSLog(@"fromvc:%f -----tovc:%f", fromvc.navAlpha, tovc.navAlpha);
CGFloat alpha = fromvc.navAlpha - (fromvc.navAlpha - tovc.navAlpha) * percentComplete;
[self setNavigationBarAlpha:alpha];
[self yhh_updateInteractiveTransition:percentComplete];
}
/*
*侧滑返回手势松手时不能pop回上一界面时调用(滑动小于一半屏幕)。
*此处context由于不知道是什么类型,里面有什么属性,成员变量所以用了runtime查看context及context的superclass.
*在context.superclass里面发现了_duration 成员变量利用kvc拿到_duration的值使用
**/
- (void)yhh_cancelInteractiveTransition:(CGFloat)percentComplete transitionContext:(id)context {
UIViewController *fromvc = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
// NSLog(@"%@", [[context superclass] getAllProperties]);
// NSLog(@"%f-%f--%@", percentComplete, [[context valueForKey:@"_duration"] floatValue] , NSStringFromClass([context class]));
[UIView animateWithDuration:[[context valueForKey:@"_duration"] floatValue] * percentComplete animations:^{
[self setNavigationBarAlpha:fromvc.navAlpha];
}];
[self yhh_cancelInteractiveTransition:percentComplete transitionContext:context];
}
/*
*侧滑返回手势松手时能够pop回上一界面时调用(滑动大于一半屏幕)
**/
- (void)yhh_finishInteractiveTransition:(CGFloat)percentComplete transitionContext:(id)context {
UIViewController *tovc = [context viewControllerForKey:UITransitionContextToViewControllerKey];
[UIView animateWithDuration:[[context valueForKey:@"_duration"] floatValue]*(1 - percentComplete) animations:^{
[self setNavigationBarAlpha:tovc.navAlpha];
}];
[self yhh_finishInteractiveTransition:percentComplete transitionContext:context];
}
设置导航栏alpha值
/*
*这里由于直接修改UINavigationBar.subviews[0]的alpha值会导致渐变过程中没有毛玻璃效果。
*#所以利用runtime查看成员变量拿到然后kvc拿到_backgroundEffectView修改其alpha
**/
- (void)setNavigationBarAlpha:(CGFloat)alpha {
UIView *backView = self.navigationBar.subviews[0];
NSLog(@"%s, %f", __func__, alpha);
UIView *shadow = [backView valueForKey:@"_shadowView"];
if (shadow) {
shadow.alpha = alpha;
}
UIView *effectView = [backView valueForKey:@"_backgroundEffectView"];
if (effectView) {
effectView.alpha = alpha;
}
}
最终效果:
附上gitub链接