一、寫在前面
好久沒(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
總之很屌絲
以上情景純屬虛構(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è)置芳来,所以需注意。