神乎其技的导航栏透明度渐变

一、写在前面

好久没有更新文章了,因为自身时间安排的原因,而且当你着手写的时候才发现,要把一系列不那么简单的逻辑用文字表述明白,真的很难。就这篇文章浮夸的标题,其实我就想了大概2分钟😖~

二、屌丝的自定义导航栏实现

在项目中,很多时候某些页面导航栏是不显示的,并且类似于个人信息页面,滑动过程中导航栏还会随之显示或隐藏。大多数童鞋都是这样处理的:

  • viewWillAppear:方法中设置导航栏隐藏
  • viewWillDisappear:方法中设置导航栏显示
  • 重写一个与系统导航栏等高的view,操作其alpha值

一切都是那么的完美,舒舒服服~

三、情景再现

直到某一天,产品大大心血来潮,新需求应声而到:你们iOS系统不是支持侧滑的吗?为什么咱们的App不能侧滑,赶紧加上,今天发个新包
对于一个看似简单的需求,很多屌丝会立马说一句:so easy~,于是咔咔咔一通code。打包测试的时候,测试妹纸会很快发现下面这样的一个bug

bug1[图片上传中...(2.gif-5575aa-1541423903119-0)]

bug2

总之很屌丝
以上情景纯属虚构,如若雷同,必是巧合

四、分析bug产生原因

比较简单,大伙儿结合上面处理的方式进行分析,就能明白了。

五、问题解决思路

方案一

把导航控制器的根控制器导航栏隐藏,使用自定义view作为导航栏。这样侧滑返回的时候,就不会有在viewWillAppear与viewWillDisAppear中操作系统导航栏是否隐藏的逻辑。从而避免了上面两种bug的产生。
缺点: 太过于屌丝,为了解决这一个bug,需要将根控制器到有此需求的控制器之间的所有导航栏都隐藏,并针对每个页面画伪导航栏。并且完全舍弃了系统侧滑时导航栏动画,效果僵硬。

方案二

最近的一篇文章也有介绍过自定义转场动画的实现。我们在系统SDK提供的转场协议方法中,针对fromVC与toVC中的控件,做自定义的转场动画实现。

其实导航侧滑返回也是系统的转场的一种。但是对于同一个导航控制器下的视图控制器,导航栏透明度属性都是全局的,并不属于fromVC与toVC的任何一个。所以首先要做的就是针对试图控制器,添加一个导航栏透明度属性。

1. 通过VC的alpha,控制导航栏透明度

在runtime实现setter方法时,我们背地里实际上是操作视图控制器所在的导航控制器的导航栏的透明度。代码如下:

- (void)setNavAlpha:(CGFloat)navAlpha
{
    objc_setAssociatedObject(self, @selector(navAlpha), @(navAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self.navigationController setNavigationBackgroundAlpha:navAlpha];
}

- (void)setNavigationBackgroundAlpha:(CGFloat)alpha
{
    //1.找到导航bar上第一个背景视图
    UIView *barView = [[self.navigationBar subviews] firstObject];
    //2.kvc获取阴影视图
    UIView *shadowView = [barView valueForKey:@"_shadowView"];
    //3.如果能够获取到设置透明度
    if (shadowView) {
        shadowView.alpha = alpha;
    }
    //4.如果导航栏默认没有设置半透明,背景视图透明度也进行改变
    if (!self.navigationBar.isTranslucent) {
        barView.alpha = alpha;
        return;
    }
    
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
        
        UIView *backEffectView = [barView valueForKey:@"_backgroundEffectView"];
        if (backEffectView && [self.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault] == nil) {
            
            backEffectView.alpha = alpha;
        }
    } else {
        
        UIView *daptiveBackdrop = [barView valueForKey:@"_adaptiveBackdrop"];
        UIView *backdropEffectView = [daptiveBackdrop valueForKey:@"_backdropEffectView"];
        if (daptiveBackdrop != nil && backdropEffectView != nil ) {
            backdropEffectView.alpha = alpha;
        }
    }
}
2.手动Pop,动态改变导航栏透明度

重写UINavigationController的协议方法,在pop前,偷偷地操作导航栏。

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    UIViewController *topVC = self.topViewController;
    id <UIViewControllerTransitionCoordinator> transitionCtx = topVC.transitionCoordinator;
    if (topVC && transitionCtx && transitionCtx.initiallyInteractive) {
        
        if ([[UIDevice currentDevice].systemVersion floatValue]>=10.0) {
            
            [transitionCtx notifyWhenInteractionChangesUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
                //因为侧滑返回也会执行此协议方法,所以在这里处理手势专场取消的情况
                [self dealInteractionChanges:context];
            }];
        } else {
            
            [transitionCtx notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
                //因为侧滑返回也会执行此协议方法,所以在这里处理手势专场取消的情况
                [self dealInteractionChanges:context];
            }];
        }
        return YES;
    }
    UIViewController *popToVc = self.viewControllers[self.viewControllers.count - 2];
    [self popToViewController:popToVc animated:YES];
    return YES;
}

- (void)dealInteractionChanges:(id <UIViewControllerTransitionCoordinatorContext>)context
{
    void(^animations)(NSString *) = ^(NSString *key) {
      
        CGFloat nowAlpha = [context viewControllerForKey:key].navAlpha;
        [self setNavigationBackgroundAlpha:nowAlpha];
        self.navigationBar.tintColor = [context viewControllerForKey:key].navTintColor;
//        self.navigationBar.barTintColor = [context viewControllerForKey:key].navBarTintColor;
    };
    if (context.isCancelled) {
        
        //拖动取消,使用toVC的属性相关
        NSTimeInterval cancaleDuration = context.transitionDuration * context.percentComplete;
        [UIView animateWithDuration:cancaleDuration animations:^{
            animations(UITransitionContextFromViewControllerKey);
        }];
    } else {
        
        //正常拖动,使用fromVC的属性相关
        NSTimeInterval finishDuration = context.transitionDuration * (1 - context.percentComplete);
        [UIView animateWithDuration:finishDuration animations:^{
            animations(UITransitionContextToViewControllerKey);
        }];
    }
}

因为侧滑返回也会执行此协议方法,而侧滑返回不同于手动返回的一点就是,侧滑返回中途有可能cancel。所以上面的方法根据转场上下文协议是否cancel。来确定最后使用fromVC的alpha还是toVC的alpha

3.侧滑Pop,动态改变导航栏透明度

根据上面两点的实现,效果如下:


目前的效果

可以看到,手动侧滑的过程中,缺少了渐变的效果。在之前的自定义转场动画中,我们知道转场过程中,有一个方法会持续执行。

- (void)updateInteractiveTransition:(CGFloat)percentComplete;

使用runtime对此方法进行方法交换,在我们交换的方法中,根据进度percentComplete对导航栏alpha做动态改变处理:

- (void)xll_updateInteractiveTransition:(CGFloat)percentComplete
{
    UIViewController *topVC = self.topViewController;
    if (!topVC) return;
    //1.获取转场上下文协议
    id <UIViewControllerTransitionCoordinator>transitionCtx = topVC.transitionCoordinator;
    //2.根据转场上下文协议获取转场始末控制器
    UIViewController *fromVC = [transitionCtx viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionCtx viewControllerForKey:UITransitionContextToViewControllerKey];
    //3.获取始末控制器导航栏透明度
    CGFloat fromAlpha = fromVC.navAlpha;
    CGFloat toAlpha = toVC.navAlpha;
    //4.计算出转场过程中变化的透明度值
    CGFloat newAlpha = fromAlpha+(toAlpha-fromAlpha)*percentComplete;
    //5.重新设定透明度
    [self setNavigationBackgroundAlpha:newAlpha];  
    [self xll_updateInteractiveTransition:percentComplete];
}

效果如下:


最终效果图

六、后期思考

导航栏上不仅有导航栏透明度,还有导航栏背景颜色,导航标题大小与颜色,导航左右item内容颜色,状态栏样式等。这些都有可能在相邻的两个页面不同。
文章也是匆忙写的,讲述的也比较笼统,小伙伴们结合Demo会更有效地明白我要表达的意思这是Demo,后期也会慢慢将以上所考虑到的实现代码加进去。希望发现问题及时指正,共同进步🙂

七、更新

之前文章最后提出了自己的设想,已经对其进行了实现。并且整理相关的代码,使其能够方便地移植到现有项目中。更新点如下:

  • 代码部分重构,更方便地移植到项目中
  • 对segue线跳转,或者代码跳转。进行了兼容
  • 对系统侧滑,或者自定义侧滑。进行了兼容。
  • 默认的navigationItem,或者自定义的navigationItem。都可在两个页面中变化
    默认的navigationItem可以设置控制器的navTintColor进行渐变。自定义的navigationItem可以分别设置其背景图片。
    注意!!!

因为导航栏透明度不为1的时候,根控制器最好在导航栏底下(注意不是下方,是顶部与导航栏一齐)。否则肯定是不符合需求的,我不相信有哪个项目会让某个页面导航栏部分是一片透明,没有任何元素。
要控制导航栏顶部与根控制器顶部一齐。要设置navigationBar.translucent = YES,并且vc.edgesForExtendedLayout = UIRectEdgeTop,并且导航栏不能被隐藏。所以代码中进行了以下设置:

- (void)setNavAlpha:(CGFloat)navAlpha
{
    objc_setAssociatedObject(self, @selector(navAlpha), @(navAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (navAlpha < 1)
    {
        //必须满足这两个条件,导航根控制器顶部才能在导航栏底下
        //否则就没有意义了
        self.navigationController.navigationBar.translucent = YES;
        self.edgesForExtendedLayout = UIRectEdgeTop;
    }
    [self.navigationController setNavigationBackgroundAlpha:navAlpha];
}

这两坨东西都可在IB设置。更有甚者,导航栏半透明度还可以在plist文件中设置,所以需注意。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容