轉(zhuǎn)載自:http://www.reibang.com/p/6a5552ec5099#
iOS中UIViewController對象如果通過push方式呈現(xiàn)拦宣,是由UINavigationController利用類棧結(jié)構(gòu)去維護(hù)的挽霉;而UINavigationBar則是“寄生”在是UINavigationController上的屬性對象,但棧頂?shù)腢IViewController對象卻可以操作自身navigationItem屬性(UINavigationItem對象)去決定這么多controller共享的UINavigationBar的視覺和交互表現(xiàn)……一言以蔽之厅各,貴圈真亂宪彩!但UINavigationBar這么較特殊的存在,落到程序猿手中耐齐,還是得老老實實為需求服務(wù)厂抽。且恕筆者才疏學(xué)淺察滑,就不展開那么多啦,單單就聊一聊關(guān)于UINavigationBar背景色的那些事修肠。
曲徑初探
閑話休表贺辰,直接拎出可能會影響UINavigationBar背景色表現(xiàn)的那些屬性瞧瞧吧
1. backgroundColor
這個不用多介紹了,從UIView基類上繼承而來嵌施,最常用的背景色設(shè)置手段饲化,可惜UINavigationBar偏偏不那么尋常
先給UINaigationBar設(shè)backgroundColor為純綠色,
self.navigationController.navigationBar.backgroundColor= [UIColorgreenColor];
實際效果如下:
greenBg.png
這種朦朧的味道吗伤,仿佛是三月春風(fēng)吹拂過茫茫草原……呃吃靠,可是這明顯不是純正綠色啊足淆?
碰上這種幕后的小動作巢块,就該Xcode自帶神器Debug View Hierarchy派上用場了礁阁,讓我們瞧瞧是誰在里面搗亂?
(此處以iOS 9.3為例族奢,但iOS10中導(dǎo)航欄結(jié)構(gòu)其實發(fā)生了變化姥闭,但層級相似)
居然純綠色的UINavigationBar前面還有好幾層啊,最可惡的就是淡綠色的那層越走,完全遮蓋住了那純正的味道棚品。從View Hierarchy可見,這個玩意原來是名為_UIBackdropEffectView的某個私有類對象廊敌,而且還非UINavigationBar的直接子視圖铜跑,中間還隔了_UIBackdropView類對象。果然幕后好多見不得人的勾當(dāng)……
接下再找另外一個頁面練練手骡澈,如法炮制锅纺,設(shè)置UINaigationBar其backgroundColor為純紅色
self.navigationController.navigationBar.backgroundColor= [UIColorredColor];
但是效果卻神奇的發(fā)生了變化!見下圖
呵呵肋殴,隔著屏幕放佛也能聽到某些人內(nèi)心OS:這傻子連這都寫不對……但我以蘋果爸爸的聲譽起誓伞广,代碼寫的沒錯!那問題是出在哪呢疼电?
正在苦苦思索中的我不小心瞥到了這么一句代碼:
self.navigationBar.translucent=NO;
貌似translucent屬性默認(rèn)值為YES吧,難道是這家伙在搗鬼减拭?那么就先把這個屬性給扒個干凈吧
2. translucent
@property(nonatomic,assign,getter=isTranslucent) BOOL translucent NS_AVAILABLE_IOS(3_0) UI_APPEARANCE_SELECTOR; // Default is NO on iOS 6 and earlier. Always YES if barStyle issettoUIBarStyleBlackTranslucentDescription? ? ABooleanvalueindicating whether the navigation baristranslucent (YES)ornot(NO).ThedefaultvalueisYES.Ifthe navigation bar has a custom background image, thedefaultisYESifanypixelofthe image has an alphavalueoflessthan1.0,andNOotherwise.Ifyousetthis propertytoYESona navigation barwithanopaquecustom background image, the navigation bar willapplyasystemopacitylessthan1.0tothe image.Ifyousetthis propertytoNOona navigation barwitha translucent custom background image, the navigation bar provides anopaquebackgroundforthe imageusingblackifthe navigation bar has UIBarStyleBlackstyle, whiteifthe navigation bar has UIBarStyleDefault,orthe navigation bar’s barTintColorifa customvalueisdefined.AvailabilityiOS (3.0andlater), tvOS (3.0andlater)
原來這家伙會根據(jù)UINavigationBar設(shè)置的自定義的背景圖片(見setBackgroundImage:forBarMetrics:方法)蔽豺,去判定是否為背景圖添加透明度!
根據(jù) 1 中的探究拧粪,translucent屬性還會影響UINavigationBar backgroundColor的體現(xiàn)與否修陡,不僅如此,諸位還記得UIViewController在iOS7.0中引入的如下兩個屬性嗎:
@property(nonatomic,assign)UIRectEdgeedgesForExtendedLayoutNS_AVAILABLE_IOS(7_0);// Defaults to UIRectEdgeAll@property(nonatomic,assign)BOOLextendedLayoutIncludesOpaqueBarsNS_AVAILABLE_IOS(7_0);// Defaults to NO, but bars are translucent by default on 7_0.
特別是extendedLayoutIncludesOpaqueBars可霎,在iOS7.0后默認(rèn)為NO魄鸦,然而若UINavigationBar translucent屬性為YES,則UINavigationController其topViewControll.view是包含UINavigationBar和UIStatusBar下面覆蓋的那片區(qū)域的癣朗。但若translucent屬性為NO拾因,則除非設(shè)置controller的extendedLayoutIncludesOpaqueBars屬性為YES,topViewControll.view都是不包含此區(qū)域的旷余。
這也是經(jīng)常見到與頁面大小一致的控件卻往往會有兩種不同的frame指定方式的原因:
self.tableView= [[UITableViewalloc] initWithFrame:CGRectMake(0, NAV_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT-NAV_HEIGHT) style:UITableViewStylePlain];或self.tableView= [[UITableViewalloc] initWithFrame:CGRectMake(0,0, SCREEN_WIDTH,self.view.bounds.size.height) style:UITableViewStylePlain];
平心而論绢记,我個人以為同一個iOS App項目內(nèi),最好統(tǒng)一規(guī)范controller這些屬性設(shè)置正卧,以免這兩種思路碰撞出的不是火花蠢熄,而是排版錯亂的各種bug……
既然使用backgroundColor可能會受其他屬性的干擾,那還是要嘗試一些能更直接了當(dāng)?shù)姆绞铰酰热缦旅孢@個原生方法~
3. setBackgroundImage:forBarMetrics:方法
其完全體聲明如下
- (void)setBackgroundImage:(nullableUIImage*)backgroundImage forBarMetrics:(UIBarMetrics)barMetricsNS_AVAILABLE_IOS(5_0)UI_APPEARANCE_SELECTOR;
看到這個方法签孔,我們不禁陷入沉思:既然可以設(shè)置背景圖叉讥,那么用純色去填充生成UIImage對象,然后再利用該方法不就解決了問題嗎饥追?思路很簡單图仓,實現(xiàn)很明了:
//KPAppImage+ (UIImage*)createImageWithColor:(UIColor*)color size:(CGSize)size {CGRectrect =CGRectMake(0.0f,0.0f, size.width, size.height);UIGraphicsBeginImageContext(rect.size);CGContextRefcontext =UIGraphicsGetCurrentContext();CGContextSetFillColorWithColor(context, [colorCGColor]);CGContextFillRect(context, rect);UIImage*theImage =UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();returntheImage;}//UINavigationBar+Color- (void)KPSetBackgroundColor:(UIColor*)color{UIImage*img = [KPAppImage createImageWithColor:color size:CGSizeMake(1,1)];? ? ? [selfsetBackgroundImage: forBarMetrics:UIBarMetricsDefault];}
嗯,參照視覺大大要求判耕,設(shè)置背景色為0xf8f8f8的效果新鮮出爐透绩!
呃,怎么感覺有那么一絲不對勁壁熄???
毋需動用視覺大大的像素眼帚豪,我們已經(jīng)發(fā)現(xiàn)了端倪!
這不是列表支持下拉刷新的loading indicator嗎草丧,居然還薄紗披身半遮面……
哎狸臣,不用多說,八成還是translucent屬性搞的鬼昌执!
立馬把這些可惡的UINavigationBar的translucent屬性改為NO烛亦,沒想到不一會各路bug如??般翩然而至:“消息頁面怎么導(dǎo)航欄底下留了一塊空白?”“為啥頁面排版都錯亂懂拾?”“iOS同學(xué)你們在搞神馬煤禽,沒有bug都改出bug了!”……
飽含著眼淚的程序猿啊岖赋,默默的把代碼回滾了……哎檬果,剛剛還提醒大家注意呢,結(jié)果自己踩進(jìn)大坑唐断,各路UINavigationController translucent屬性設(shè)置不一致的歷史遺留問題太可惡了选脊,任務(wù)這么緊急,可再不敢隨便改動了脸甘。那怎么才能讓效果過得了視覺大大的像素眼呢恳啥?只好再去瞅瞅UINavigationBar的視圖層級結(jié)構(gòu),看看有啥治病偏方木有
洞天石扉丹诀,訇然中開
4. 探查UINavigationBar View Hierarchy(before iOS10 vs iOS10)
借助Xcode View Hierarchy Debug工具钝的,查看僅通過方案3設(shè)置背景色且translucent屬性為YES的UINavigationBar對象,可得iOS10之前的視圖結(jié)構(gòu)如下:
而iOS10的UINavigationBar視圖結(jié)構(gòu)如圖:
同時打印選中的UIImageView對象其description如下铆遭,可見是默認(rèn)其alpha通道非1.0即略微透明:
> - (null)
當(dāng)然扁藕,如果關(guān)閉translucent屬性,自然不會有導(dǎo)航欄透視的問題疚脐,但假如是存在前述的“歷史遺留問題”亿柑,這種修正影響頁面會比較龐多的情況,有沒有附帶傷害更小的解決方案呢棍弄?
自然是天無絕人之路望薄,程序猿們總能想出一些詭計來實現(xiàn)期望的效果疟游。很明顯,如若避免透視導(dǎo)航欄其層級之下內(nèi)容痕支,讓其渲染背景色的視圖(before iOS10:_UINavigationBarBackground, iOS10:_UIBarBackground)以及其子視圖不透明就可以了嘛颁虐,那么自然會有兩個方案:
1.把這些alpha值不為1.0的控件設(shè)置為不透明
2.利用別的視圖遮蔽掉這些透明控件
很不幸的是,我對方案1的嘗試失敗了卧须,特別是有設(shè)置backgroundColor的情況下另绩,UINavigationBar其subviews(或迭代包含的subviews)還包括了_UIBackDropView(before iOS10)、UIVisualEffectView(iOS10)等模糊效果控件花嘶,對其設(shè)置alpha或背景色效果可能無效甚至表現(xiàn)有問題笋籽,故此方案暫告一段落。
那么方案2的表現(xiàn)呢椭员?
啊哦车海,終于達(dá)成了為導(dǎo)航欄完美設(shè)置0xf8f8f8背景色的需求!
再來剖析一下此時的View Hierarchy
Paste_Image.png
眼尖的同學(xué)可能發(fā)現(xiàn)隘击,與之前的View Hierarchy相比侍芝,_UIBarBackground的subviews中似乎多了一個UIView對象——沒錯,這個UIView對象即擔(dān)負(fù)著填充背景色且遮擋可能出現(xiàn)模糊透視的任務(wù)的關(guān)鍵視圖埋同。
Talk is cheap, show me the code~~
- (UIView*)overlay{returnobjc_getAssociatedObject(self, &overlayKey);}- (void)setOverlay:(UIView*)overlay{? ? objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (void)KPSetBackgroundColor:(UIColor*)backgroundColor{if(!self.overlay) {? ? ? ? [selfsetBackgroundImage:[UIImagenew] forBarMetrics:UIBarMetricsDefault];UIView*backgroundView = [selfKPGetBackgroundView];self.overlay= [[UIViewalloc] initWithFrame:backgroundView.bounds];self.overlay.userInteractionEnabled=NO;self.overlay.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;? ? ? ? [backgroundView insertSubview:self.overlayatIndex:0];? ? }self.overlay.backgroundColor= backgroundColor;}- (UIView*)KPGetBackgroundView{//iOS10之前為 _UINavigationBarBackground, iOS10為 _UIBarBackground//_UINavigationBarBackground實際為UIImageView子類州叠,而_UIBarBackground是UIView子類//之前setBackgroundImage直接賦值給_UINavigationBarBackground,現(xiàn)在則是設(shè)置后為_UIBarBackground增加一個UIImageView子控件方式去呈現(xiàn)圖片if([currentSystemVersion floatValue] >=10.0) {UIView*_UIBackground;NSString*targetName =@"_UIBarBackground";? ? ? ? Class _UIBarBackgroundClass=NSClassFromString(targetName);for(UIView*subviewinself.subviews) {if([subview isKindOfClass:_UIBarBackgroundClass.class]) {? ? ? ? ? ? ? ? _UIBackground= subview;break;? ? ? ? ? ? }? ? ? ? }return_UIBackground;? ? }else{UIView*_UINavigationBarBackground;NSString*targetName =@"_UINavigationBarBackground";? ? ? ? Class _UINavigationBarBackgroundClass=NSClassFromString(targetName);for(UIView*subviewinself.subviews) {if([subview isKindOfClass:_UINavigationBarBackgroundClass.class]) {? ? ? ? ? ? ? ? _UINavigationBarBackground= subview;break;? ? ? ? ? ? }? ? ? ? }return_UINavigationBarBackground;? ? }}#pragma mark - shadow view- (void)KPHideShadowImageOrNot:(BOOL)bHidden{UIView*bgView = [selfKPGetBackgroundView];//shadowImage應(yīng)該是只占一個像素凶赁,即1.0/scalefor(UIView*subviewinbgView.subviews) {if(CGRectGetHeight(subview.bounds) <=1.0) {? ? ? ? ? ? subview.hidden= bHidden;? ? ? ? }? ? }}
設(shè)置overlay的設(shè)計是比較普遍使用的一種辦法咧栗,但上述實現(xiàn)還針對iOS10之前與之后UINavigationBar不同的View Hierarchy去更精準(zhǔn)地插入overlay;同時還可以很方便的定位導(dǎo)航欄下方的陰影條哟冬,可以隨心所欲地設(shè)置其隱藏或顯現(xiàn)。
歸去來兮
綜上所述忆绰,僅僅是設(shè)置UINavigationBar背景色這么簡簡單單的效果浩峡,卻可能會涉及這么多關(guān)聯(lián)因素。
不得不感嘆错敢,縱然牛逼如蘋果翰灾,把UI相關(guān)的設(shè)計和接口做到純粹的簡約清晰也還是很有難度。(以上語錄都可以入選裝逼遭雷劈的典型案例了吧……??)
但是忽然想到稚茅,冥冥之中居然還遺漏了UINavigationBar的一個關(guān)鍵屬性纸淮,即barTintColor
@property(nullable,nonatomic,strong)UIColor*barTintColorNS_AVAILABLE_IOS(7_0);Description? ? The tint color to apply to the navigation bar background.This color is made translucent bydefaultunless you set the translucent property toNO.
看描述似乎我們上面那一番勞累難道白費了?亚享?拿事實說話咽块,來瞧一瞧為translucent為YES的UINavigationBar對象設(shè)置其barTintColor為0xf8f8f8 RGB色值后的表現(xiàn):
效果居然出乎意料的不錯。不過用xscope仔細(xì)探查像素發(fā)現(xiàn)欺税,雖然顏色看起來差不多侈沪,但實際取到色值卻是在0xf4f4f4~0xf9f9f9之間揭璃,并不是完全純正的0xf8f8f8。這是怎么回事呢亭罪?照舊Debug View Hierarchy大法好~
(此處以iOS 10作為測試版本)
又出現(xiàn)了UIVisualEffectView這家伙瘦馍!前面提到過,它是負(fù)責(zé)產(chǎn)生模糊透明特效的应役,而View Hierarchy中看到它自己本身是透明的情组,關(guān)鍵還是_UIVisualEffectBackdropView以及兩個_UIVisualEffectFilterView共3個私有子視圖,且明顯可以看出箩祥,前者是有實時模糊生成(根據(jù)其層級之下的展現(xiàn)內(nèi)容驗算)院崇,中者則是設(shè)置了半透明的背景色,后者則為純正的0xf8f8f8顏色但alpha值不為1滥比,這兩者背景色合成之后的結(jié)果亚脆,才代表最終展現(xiàn)的導(dǎo)航欄顏色(不管你信不信,反正我是暈了)……利用console debug命令探查這三者alpha值盲泛、背景色屬性如下
//_UIVisualEffectBackdropView對象(lldb) po (CGFloat)self.navigationController.navigationBar.subviews[0].subviews[1].subviews[0].alpha1(lldb) po self.navigationController.navigationBar.subviews[0].subviews[1].subviews[0].backgroundColor0x0000000000000000//第一個_UIVisualEffectFilterView對象(lldb) po (CGFloat)self.navigationController.navigationBar.subviews[0].subviews[1].subviews[1].alpha1(lldb) po self.navigationController.navigationBar.subviews[0].subviews[1].subviews[1].backgroundColorUIExtendedGrayColorSpace0.970.8//第二個_UIVisualEffectFilterView對象(lldb) po (CGFloat)self.navigationController.navigationBar.subviews[0].subviews[1].subviews[2].alpha0.85000002384185791(lldb) po self.navigationController.navigationBar.subviews[0].subviews[1].subviews[2].backgroundColorUIExtendedSRGBColorSpace0.9725490.9725490.9725491
實事也呼應(yīng)了barTintColor屬性描述中那段話
This color is made translucent by default unless you set the translucent property to NO.
恍如隔世
這么一番曲折的經(jīng)歷下來濒持,除了腦袋搞暈了之外,還能得出什么結(jié)論嗎寺滚?哎柑营,姑且以我的一家之言收尾吧:
想要為UINavigationBar設(shè)置某個色值的純色背景,則
translucent屬性為YES:
如果想要通過視覺大大一絲不茍的像素眼村视,請參照 4 中的方案2
否則若只是要求肉眼不容易發(fā)覺(請不要吐槽我的隨便)官套,請利用 barTintColor屬性
如果要求放松到無所謂的程度,透不透視都不關(guān)心蚁孔,請使用 setBackgroundImage:forBarMetrics: 方法或者干脆用 backgroundColor屬性
translucent屬性為NO:
如果想要通過視覺大大一絲不茍的像素眼奶赔,依舊參照 4 中的方案2
否則若只是要求肉眼不容易發(fā)覺(請不要吐槽我的隨便),使用barTintColor 或者 setBackgroundImage:forBarMetrics: 方法(仍然存在混色問題杠氢,例如設(shè)置純紅色站刑,view hierarchy看單個視圖取色為0xff0000,而直接從App中取色則前者效果為0xfb2930,后者效果為0xfc0d1b鼻百,相對而言后者更精準(zhǔn))
backgroundColor設(shè)置則是無效的绞旅,請放棄該方式
文/BladeWayne(簡書作者)
原文鏈接:http://www.reibang.com/p/6a5552ec5099#
下邊談?wù)勛约旱挠梅ǎ?/h1>(注:self.tabBarController.tabBar的用法和這個類似;在xib創(chuàng)建的ViewController中尤其容易出現(xiàn)這樣的問題温艇。)
最近項目的導(dǎo)航欄一直是灰色因悲,而設(shè)計師要求純白,故研究了這個文章勺爱。
做法是:
-(void)viewWillAppear:(BOOL)animated{
[superviewWillAppear:animated];
//self.extendedLayoutIncludesOpaqueBars=YES;
//如果頁面導(dǎo)航欄下邊出現(xiàn)黑條晃琳,就把上句代碼放開。印證了作者這句話:但若translucent屬性為NO,則除非設(shè)置controller的extendedLayoutIncludesOpaqueBars屬性為YES蝎土,topViewControll.view都是不包含此區(qū)域的视哑。
self.navigationController.navigationBar.translucent=NO;
}
-(void)viewWillDisappear:(BOOL)animated{
[superviewWillDisappear:animated];
self.navigationController.navigationBar.translucent=YES;
}