咱們在iOS項目開發中,有些時候須要修改標準控件的樣式,咱們今天就圍繞一個具體項目需求,進行UINavigationBar
的繼承與改造。app
咱們在UINavigationBar.h
頭文件中,看到以下修改NavigationBar背景顏色的屬性框架
@property(nullable, nonatomic,strong) UIColor *barTintColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR; // default is nil
注意到UI_APPEARANCE_SELECTOR
這個宏了麼,用這個宏標記的屬性,都是能夠經過UIApperance
協議進行全局設置的屬性。說的更直白一點,就是能夠一次性,修改項目中全部的這個類的默認屬性。字體
例如在iOS6以前,UILabel
的默認背景顏色不是透明色,而是白色。咱們就可使用以下方法,修改UILabel
的默認背景色atom
[[UILabel appearance] setBackgroundColor:[UIColor clearColor]];
UIApperance
協議就是這麼神奇,全部的UIKit控件都遵照了這個協議,全部標記了UI_APPEARANCE_SELECTOR
宏的屬性,均可以使用appearance
實例修改默認值,是否是很炫酷。spa
上面一段與本文正題無關,下面咱們看一下本文的項目需求code
這個頁面就是一個標準的NavigationController
+ TableViewContoller
組合實現的設置頁面,導航條和Table的樣式須要訂製。orm
前面說到的UIApperance
協議是能夠實現的,咱們換一種更爲廣泛的方式實現,繼承。blog
咱們繼承UINavigationBar
,建立子類FWBar
。咱們使用storyboard實例化大致框架模型,並將NavigationViewController
的NavigationBar
設置爲咱們的FWBar
類,並將UITableView
設置爲Static
靜態模式,直接編輯了Cell
的內容。繼承
在FWBar.m
中加入以下代碼教程
- (void)awakeFromNib { [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsCompact]; self.shadowImage = [UIImage new]; //把以前的View通通隱藏 [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj setHidden:YES]; }]; [self addSubview:self.fakeBackgroundView]; self.fakeBackgroundView.userInteractionEnabled = NO; [self sendSubviewToBack:self.fakeBackgroundView]; self.titleTextAttributes = @{ NSFontAttributeName: [UIFont fontWithName:@"NotoSansHans-DemiLight" size:16], NSForegroundColorAttributeName:[UIColor colorWithRed:57.0/255 green:207.0/255 blue:218.0/255 alpha:1] }; //rgba(165, 195, 205, 1) self.tintColor = [UIColor colorWithRed:165.0/255 green:195.0/255 blue:205.0/255 alpha:1]; }
解釋 由於原生的NaviBar背景View下方有一條灰色的邊,這條邊不是用layer生成的,我沒搞明白是怎麼實現的,因此直接將這個View隱藏掉了。順便吧shadowImage
也換成空圖。
這裏的self.fakeBackgroundView
是咱們添加的背景,顏色是白色。這裏咱們將它移到最下層,而且觸摸屬性關掉,userInteractionEnabled
設爲NO
。
titleTextAttributes
這個屬性,是用來修改title的樣式的。
tintColor
這個屬性,是用來修改導航條左右按鈕顏色的。
這些操做作完,還不夠。
咱們沒法經過暴露出來的接口修改左右按鈕的字體和位置。這也是咱們選擇繼承而不是UIApperance的緣由
- (void)didAddSubview:(UIView *)subview { NSLog(@"%@",subview); if ([subview isKindOfClass:NSClassFromString(@"UINavigationButton")]) { if ([subview isKindOfClass:[UIButton class]]) { [(UIButton*)subview setAttributedTitle:[[NSAttributedString alloc] initWithString:[(UIButton*)subview titleForState:UIControlStateNormal] attributes:@{ NSFontAttributeName: [UIFont fontWithName:@"AvenirNext-Regular" size:17], NSForegroundColorAttributeName:self.tintColor }] forState:UIControlStateNormal]; } } } - (void)layoutSubviews { [super layoutSubviews]; [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) { if ([subview isKindOfClass:NSClassFromString(@"UINavigationButton")]) { if ([subview isKindOfClass:[UIButton class]] && subview.frame.origin.x < self.frame.size.width/2) { [subview setFrame:({ CGRect rect = subview.frame; rect.origin.x = 8; rect.size.width = 69; rect; })]; } } }]; }
解釋 重寫- (void)didAddSubview:(UIView *)subview
方法,檢測了系統控件根據NavigationItem
向NavigationBar
添加按鈕這個事件,而後對按鈕進行甄別,定製。
咱們找到Cancel
這個按鈕,他雖然是UINavigationButton
類型,可是必定是繼承了UIButton
,因此咱們直接強轉成她的父類,修改其文字字體和frame。
重寫layoutSubviews
這個方法,是爲了實時更新咱們的按鈕位置。這個其實也能夠不更改的,可是咱們的項目需求中,Cancel
這個字段太長,字體變大之後致使了顯示不全,因此咱們將這個作按鈕的frame變大了。
注意幾點
NSClassFromString(@"UINavigationButton")
這個方法是咱們沒法獲取內部類的時候,獲取Class類型的方法。UINavigationButton
這個類名是NSLog輸出時看到的。
這一段使用了特殊的語法糖,有興趣瞭解的參考這篇sunnyxx大神的博文,全文搜索關鍵字小括號內聯複合表達式
[subview setFrame:({ CGRect rect = subview.frame; rect.origin.x = 8; rect.size.width = 69; rect; })];
最後的實現效果。
截屏的效果不是太好,細心的朋友可能會發現,咱們的FWBar
在TableView
向上滑動的過程當中會漸出陰影。
我把這段代碼分享給你們,可是這段代碼偷懶沒用KVO,而是用了ReactiveCocoa
這個龐大的龐大框架的小小功能,因此,就沒放倒教程裏。
- (void)didMoveToSuperview { [super didMoveToSuperview]; UIViewController *presentingViewController = [UIApplication sharedApplication].keyWindow.rootViewController; while (presentingViewController.presentedViewController) presentingViewController = presentingViewController.presentedViewController; __block BOOL has = NO; [[presentingViewController childViewControllers] enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj isKindOfClass:[UINavigationController class]]) { [[obj childViewControllers] enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull obj2, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj2 isKindOfClass:[UITableViewController class]]) { has = YES; UITableViewController* tVC = obj2; if (self.tableViewOffsetDisposable) { [self.tableViewOffsetDisposable dispose]; } self.tableViewOffsetDisposable = [RACObserve(tVC.tableView, contentOffset) subscribeNext:^(id x) { CGPoint p = [x CGPointValue]; if (p.y <= 0 && p.y >= - 64) { self.fakeBackgroundView.layer.shadowOpacity = fabs(64 + p.y) / 64 * 0.7; } else if (p.y > 0) { if (self.fakeBackgroundView.layer.shadowOpacity != 0.7) { self.fakeBackgroundView.layer.shadowOpacity = 0.7; } } else { if (self.fakeBackgroundView.layer.shadowOpacity != 0) { self.fakeBackgroundView.layer.shadowOpacity = 0; } } }]; } }]; } }]; }