神乎其技的導(dǎo)航欄透明度漸變

一、寫在前面

好久沒(méi)有更新文章了舶吗,因?yàn)樽陨頃r(shí)間安排的原因誓琼,而且當(dāng)你著手寫的時(shí)候才發(fā)現(xiàn)腹侣,要把一系列不那么簡(jiǎn)單的邏輯用文字表述明白齿穗,真的很難缤灵。就這篇文章浮夸的標(biāo)題腮出,其實(shí)我就想了大概2分鐘??~

二、屌絲的自定義導(dǎo)航欄實(shí)現(xiàn)

在項(xiàng)目中作儿,很多時(shí)候某些頁(yè)面導(dǎo)航欄是不顯示的攻锰,并且類似于個(gè)人信息頁(yè)面,滑動(dòng)過(guò)程中導(dǎo)航欄還會(huì)隨之顯示或隱藏垒迂。大多數(shù)童鞋都是這樣處理的:

  • viewWillAppear:方法中設(shè)置導(dǎo)航欄隱藏
  • viewWillDisappear:方法中設(shè)置導(dǎo)航欄顯示
  • 重寫一個(gè)與系統(tǒng)導(dǎo)航欄等高的view,操作其alpha值

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

三吏奸、情景再現(xiàn)

直到某一天陶耍,產(chǎn)品大大心血來(lái)潮烈钞,新需求應(yīng)聲而到:你們iOS系統(tǒng)不是支持側(cè)滑的嗎?為什么咱們的App不能側(cè)滑蛾狗,趕緊加上沉桌,今天發(fā)個(gè)新包
對(duì)于一個(gè)看似簡(jiǎn)單的需求留凭,很多屌絲會(huì)立馬說(shuō)一句:so easy~偎巢,于是咔咔咔一通code压昼。打包測(cè)試的時(shí)候,測(cè)試妹紙會(huì)很快發(fā)現(xiàn)下面這樣的一個(gè)bug

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

bug2

總之很屌絲
以上情景純屬虛構(gòu),如若雷同韭山,必是巧合

四、分析bug產(chǎn)生原因

比較簡(jiǎn)單梦裂,大伙兒結(jié)合上面處理的方式進(jìn)行分析年柠,就能明白了禁舷。

五牵咙、問(wèn)題解決思路

方案一

把導(dǎo)航控制器的根控制器導(dǎo)航欄隱藏洁桌,使用自定義view作為導(dǎo)航欄侯嘀。這樣側(cè)滑返回的時(shí)候戒幔,就不會(huì)有在viewWillAppear與viewWillDisAppear中操作系統(tǒng)導(dǎo)航欄是否隱藏的邏輯。從而避免了上面兩種bug的產(chǎn)生工坊。
缺點(diǎn): 太過(guò)于屌絲王污,為了解決這一個(gè)bug昭齐,需要將根控制器到有此需求的控制器之間的所有導(dǎo)航欄都隱藏阱驾,并針對(duì)每個(gè)頁(yè)面畫偽導(dǎo)航欄怪蔑。并且完全舍棄了系統(tǒng)側(cè)滑時(shí)導(dǎo)航欄動(dòng)畫饮睬,效果僵硬。

方案二

最近的一篇文章也有介紹過(guò)自定義轉(zhuǎn)場(chǎng)動(dòng)畫的實(shí)現(xiàn)窟却。我們?cè)谙到y(tǒng)SDK提供的轉(zhuǎn)場(chǎng)協(xié)議方法中呻逆,針對(duì)fromVC與toVC中的控件咖城,做自定義的轉(zhuǎn)場(chǎng)動(dòng)畫實(shí)現(xiàn)。

其實(shí)導(dǎo)航側(cè)滑返回也是系統(tǒng)的轉(zhuǎn)場(chǎng)的一種切平。但是對(duì)于同一個(gè)導(dǎo)航控制器下的視圖控制器悴品,導(dǎo)航欄透明度屬性都是全局的苔严,并不屬于fromVC與toVC的任何一個(gè)孤澎。所以首先要做的就是針對(duì)試圖控制器覆旭,添加一個(gè)導(dǎo)航欄透明度屬性。

1. 通過(guò)VC的alpha絮供,控制導(dǎo)航欄透明度

在runtime實(shí)現(xiàn)setter方法時(shí)壤靶,我們背地里實(shí)際上是操作視圖控制器所在的導(dǎo)航控制器的導(dǎo)航欄的透明度贮乳。代碼如下:

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

- (void)setNavigationBackgroundAlpha:(CGFloat)alpha
{
    //1.找到導(dǎo)航bar上第一個(gè)背景視圖
    UIView *barView = [[self.navigationBar subviews] firstObject];
    //2.kvc獲取陰影視圖
    UIView *shadowView = [barView valueForKey:@"_shadowView"];
    //3.如果能夠獲取到設(shè)置透明度
    if (shadowView) {
        shadowView.alpha = alpha;
    }
    //4.如果導(dǎo)航欄默認(rèn)沒(méi)有設(shè)置半透明,背景視圖透明度也進(jìn)行改變
    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.手動(dòng)Pop向拆,動(dòng)態(tài)改變導(dǎo)航欄透明度

重寫UINavigationController的協(xié)議方法浓恳,在pop前,偷偷地操作導(dǎo)航欄梢夯。

- (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) {
                //因?yàn)閭?cè)滑返回也會(huì)執(zhí)行此協(xié)議方法颂砸,所以在這里處理手勢(shì)專場(chǎng)取消的情況
                [self dealInteractionChanges:context];
            }];
        } else {
            
            [transitionCtx notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
                //因?yàn)閭?cè)滑返回也會(huì)執(zhí)行此協(xié)議方法人乓,所以在這里處理手勢(shì)專場(chǎng)取消的情況
                [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) {
        
        //拖動(dòng)取消色罚,使用toVC的屬性相關(guān)
        NSTimeInterval cancaleDuration = context.transitionDuration * context.percentComplete;
        [UIView animateWithDuration:cancaleDuration animations:^{
            animations(UITransitionContextFromViewControllerKey);
        }];
    } else {
        
        //正常拖動(dòng)保屯,使用fromVC的屬性相關(guān)
        NSTimeInterval finishDuration = context.transitionDuration * (1 - context.percentComplete);
        [UIView animateWithDuration:finishDuration animations:^{
            animations(UITransitionContextToViewControllerKey);
        }];
    }
}

因?yàn)閭?cè)滑返回也會(huì)執(zhí)行此協(xié)議方法,而側(cè)滑返回不同于手動(dòng)返回的一點(diǎn)就是蝠猬,側(cè)滑返回中途有可能cancel榆芦。所以上面的方法根據(jù)轉(zhuǎn)場(chǎng)上下文協(xié)議是否cancel喘鸟。來(lái)確定最后使用fromVC的alpha還是toVC的alpha

3.側(cè)滑Pop,動(dòng)態(tài)改變導(dǎo)航欄透明度

根據(jù)上面兩點(diǎn)的實(shí)現(xiàn)崎淳,效果如下:


目前的效果

可以看到拣凹,手動(dòng)側(cè)滑的過(guò)程中嚣镜,缺少了漸變的效果橘蜜。在之前的自定義轉(zhuǎn)場(chǎng)動(dòng)畫中,我們知道轉(zhuǎn)場(chǎng)過(guò)程中跌捆,有一個(gè)方法會(huì)持續(xù)執(zhí)行疹蛉。

- (void)updateInteractiveTransition:(CGFloat)percentComplete;

使用runtime對(duì)此方法進(jìn)行方法交換,在我們交換的方法中可款,根據(jù)進(jìn)度percentComplete對(duì)導(dǎo)航欄alpha做動(dòng)態(tài)改變處理:

- (void)xll_updateInteractiveTransition:(CGFloat)percentComplete
{
    UIViewController *topVC = self.topViewController;
    if (!topVC) return;
    //1.獲取轉(zhuǎn)場(chǎng)上下文協(xié)議
    id <UIViewControllerTransitionCoordinator>transitionCtx = topVC.transitionCoordinator;
    //2.根據(jù)轉(zhuǎn)場(chǎng)上下文協(xié)議獲取轉(zhuǎn)場(chǎng)始末控制器
    UIViewController *fromVC = [transitionCtx viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionCtx viewControllerForKey:UITransitionContextToViewControllerKey];
    //3.獲取始末控制器導(dǎo)航欄透明度
    CGFloat fromAlpha = fromVC.navAlpha;
    CGFloat toAlpha = toVC.navAlpha;
    //4.計(jì)算出轉(zhuǎn)場(chǎng)過(guò)程中變化的透明度值
    CGFloat newAlpha = fromAlpha+(toAlpha-fromAlpha)*percentComplete;
    //5.重新設(shè)定透明度
    [self setNavigationBackgroundAlpha:newAlpha];  
    [self xll_updateInteractiveTransition:percentComplete];
}

效果如下:


最終效果圖

六筋讨、后期思考

導(dǎo)航欄上不僅有導(dǎo)航欄透明度悉罕,還有導(dǎo)航欄背景顏色立镶,導(dǎo)航標(biāo)題大小與顏色媚媒,導(dǎo)航左右item內(nèi)容顏色,狀態(tài)欄樣式等栈顷。這些都有可能在相鄰的兩個(gè)頁(yè)面不同嵌巷。
文章也是匆忙寫的搪哪,講述的也比較籠統(tǒng),小伙伴們結(jié)合Demo會(huì)更有效地明白我要表達(dá)的意思這是Demo,后期也會(huì)慢慢將以上所考慮到的實(shí)現(xiàn)代碼加進(jìn)去颤难。希望發(fā)現(xiàn)問(wèn)題及時(shí)指正已维,共同進(jìn)步??

七垛耳、更新

之前文章最后提出了自己的設(shè)想飘千,已經(jīng)對(duì)其進(jìn)行了實(shí)現(xiàn)护奈。并且整理相關(guān)的代碼哥纫,使其能夠方便地移植到現(xiàn)有項(xiàng)目中蛀骇。更新點(diǎn)如下:

  • 代碼部分重構(gòu)擅憔,更方便地移植到項(xiàng)目中
  • 對(duì)segue線跳轉(zhuǎn),或者代碼跳轉(zhuǎn)蚌讼。進(jìn)行了兼容
  • 對(duì)系統(tǒng)側(cè)滑个榕,或者自定義側(cè)滑笛洛。進(jìn)行了兼容。
  • 默認(rèn)的navigationItem苛让,或者自定義的navigationItem狱杰。都可在兩個(gè)頁(yè)面中變化
    默認(rèn)的navigationItem可以設(shè)置控制器的navTintColor進(jìn)行漸變仿畸。自定義的navigationItem可以分別設(shè)置其背景圖片朗和。
    注意!GО!忆植!

因?yàn)閷?dǎo)航欄透明度不為1的時(shí)候,根控制器最好在導(dǎo)航欄底下(注意不是下方耀里,是頂部與導(dǎo)航欄一齊)冯挎。否則肯定是不符合需求的,我不相信有哪個(gè)項(xiàng)目會(huì)讓某個(gè)頁(yè)面導(dǎo)航欄部分是一片透明趾徽,沒(méi)有任何元素附较。
要控制導(dǎo)航欄頂部與根控制器頂部一齊拒课。要設(shè)置navigationBar.translucent = YES,并且vc.edgesForExtendedLayout = UIRectEdgeTop早像,并且導(dǎo)航欄不能被隱藏卢鹦。所以代碼中進(jìn)行了以下設(shè)置:

- (void)setNavAlpha:(CGFloat)navAlpha
{
    objc_setAssociatedObject(self, @selector(navAlpha), @(navAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (navAlpha < 1)
    {
        //必須滿足這兩個(gè)條件劝堪,導(dǎo)航根控制器頂部才能在導(dǎo)航欄底下
        //否則就沒(méi)有意義了
        self.navigationController.navigationBar.translucent = YES;
        self.edgesForExtendedLayout = UIRectEdgeTop;
    }
    [self.navigationController setNavigationBackgroundAlpha:navAlpha];
}

這兩坨東西都可在IB設(shè)置秒啦。更有甚者余境,導(dǎo)航欄半透明度還可以在plist文件中設(shè)置芳来,所以需注意。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佣盒,一起剝皮案震驚了整個(gè)濱河市沼撕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌磨总,老刑警劉巖蚪燕,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奔浅,居然都是意外死亡馆纳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門汹桦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲁驶,“玉大人,你說(shuō)我怎么就攤上這事舞骆≡客洌” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵督禽,是天一觀的道長(zhǎng)脆霎。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狈惫,這世上最難降的妖魔是什么忆肾? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任蔑滓,我火速辦了婚禮键袱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚜迅。我一直安慰自己谁不,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般尉共。 火紅的嫁衣襯著肌膚如雪爸邢。 梳的紋絲不亂的頭發(fā)上浇辜,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音卑雁,去河邊找鬼。 笑死扣甲,一個(gè)胖子當(dāng)著我的面吹牛启泣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坠敷,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼胰耗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼卖漫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起突委,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤敌蚜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后帅韧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淮阐,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弊决。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昆稿,死狀恐怖该溯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹭秋,我是刑警寧澤实昨,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布曙咽,位于F島的核電站例朱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜婉弹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望良姆。 院中可真熱鬧闲延,春花似錦、人聲如沸叮贩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春猜绣,著一層夾襖步出監(jiān)牢的瞬間伟阔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工傻昙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叠荠。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忍燥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容