UINavigationBar的繼承與定制
我們?cè)趇OS項(xiàng)目開發(fā)中,有些時(shí)候需要修改標(biāo)準(zhǔn)控件的樣式,我們今天就圍繞一個(gè)具體項(xiàng)目需求,進(jìn)行UINavigationBar的繼承與改造。
UIApperance協(xié)議屬性定制
我們?cè)?code>UINavigationBar.h頭文件中,看到如下修改NavigationBar背景顏色的屬性
@property(nullable, nonatomic,strong) UIColor *barTintColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR; // default is nil
注意到UI_APPEARANCE_SELECTOR這個(gè)宏了么,用這個(gè)宏標(biāo)記的屬性,都是可以通過UIApperance協(xié)議進(jìn)行全局設(shè)置的屬性。說的更直白一點(diǎn),就是可以一次性,修改項(xiàng)目中所有的這個(gè)類的默認(rèn)屬性。
例如在iOS6之前,UILabel的默認(rèn)背景顏色不是透明色,而是白色。我們就可以使用如下方法,修改UILabel的默認(rèn)背景色
[[UILabel appearance] setBackgroundColor:[UIColor clearColor]];
UIApperance協(xié)議就是這么神奇,所有的UIKit控件都遵守了這個(gè)協(xié)議,所有標(biāo)記了UI_APPEARANCE_SELECTOR宏的屬性,都可以使用appearance實(shí)例修改默認(rèn)值,是不是很炫酷。
項(xiàng)目需求
上面一段與本文正題無關(guān),下面我們看一下本文的項(xiàng)目需求

分析
這個(gè)頁面就是一個(gè)標(biāo)準(zhǔn)的NavigationController + TableViewContoller組合實(shí)現(xiàn)的設(shè)置頁面,導(dǎo)航條和Table的樣式需要訂制。
前面說到的UIApperance協(xié)議是可以實(shí)現(xiàn)的,我們換一種更為普遍的方式實(shí)現(xiàn),繼承。
我們繼承UINavigationBar,創(chuàng)建子類FWBar。我們使用storyboard實(shí)例化大體框架模型,并將NavigationViewController的NavigationBar設(shè)置為我們的FWBar類,并將UITableView設(shè)置為Static靜態(tài)模式,直接編輯了Cell的內(nèi)容。

在FWBar.m中加入如下代碼
- (void)awakeFromNib
{
[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsCompact];
self.shadowImage = [UIImage new];
//把之前的View統(tǒng)統(tǒng)隱藏
[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];
}
解釋 因?yàn)樵腘aviBar背景View下方有一條灰色的邊,這條邊不是用layer生成的,我沒搞明白是怎么實(shí)現(xiàn)的,所以直接將這個(gè)View隱藏掉了。順便吧shadowImage也換成空?qǐng)D。
這里的self.fakeBackgroundView是我們添加的背景,顏色是白色。這里我們將它移到最下層,并且觸摸屬性關(guān)掉,userInteractionEnabled設(shè)為NO。
titleTextAttributes這個(gè)屬性,是用來修改title的樣式的。
tintColor這個(gè)屬性,是用來修改導(dǎo)航條左右按鈕顏色的。
這些操作做完,還不夠。
我們無法通過暴露出來的接口修改左右按鈕的字體和位置。這也是我們選擇繼承而不是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方法,檢測(cè)了系統(tǒng)控件根據(jù)NavigationItem向NavigationBar添加按鈕這個(gè)事件,然后對(duì)按鈕進(jìn)行甄別,定制。
我們找到Cancel這個(gè)按鈕,他雖然是UINavigationButton類型,但是一定是繼承了UIButton,所以我們直接強(qiáng)轉(zhuǎn)成她的父類,修改其文字字體和frame。
重寫layoutSubviews這個(gè)方法,是為了實(shí)時(shí)更新我們的按鈕位置。這個(gè)其實(shí)也可以不更改的,但是我們的項(xiàng)目需求中,Cancel這個(gè)字段太長,字體變大以后導(dǎo)致了顯示不全,所以我們將這個(gè)做按鈕的frame變大了。
注意幾點(diǎn)
NSClassFromString(@"UINavigationButton")這個(gè)方法是我們無法獲取內(nèi)部類的時(shí)候,獲取Class類型的方法。UINavigationButton這個(gè)類名是NSLog輸出時(shí)看到的。這一段使用了特殊的語法糖,有興趣了解的參考這篇sunnyxx大神的博文,全文搜索關(guān)鍵字
小括號(hào)內(nèi)聯(lián)復(fù)合表達(dá)式
[subview setFrame:({
CGRect rect = subview.frame;
rect.origin.x = 8;
rect.size.width = 69;
rect;
})];
最后的實(shí)現(xiàn)效果。

結(jié)語
截屏的效果不是太好,細(xì)心的朋友可能會(huì)發(fā)現(xiàn),我們的FWBar在TableView向上滑動(dòng)的過程中會(huì)漸出陰影。
我把這段代碼分享給大家,但是這段代碼偷懶沒用KVO,而是用了ReactiveCocoa這個(gè)龐大的龐大框架的小小功能,所以,就沒放到教程里。
- (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;
}
}
}];
}
}];
}
}];
}