UINavigationBar的繼承與定制
我們在iOS項目開發中,有些時候需要修改標準控件的樣式,我們今天就圍繞一個具體項目需求,進行UINavigationBar
的繼承與改造。
UIApperance協議屬性定制
我們在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
的默認背景色
[[UILabel appearance] setBackgroundColor:[UIColor clearColor]];
UIApperance
協議就是這么神奇,所有的UIKit控件都遵守了這個協議,所有標記了UI_APPEARANCE_SELECTOR
宏的屬性,都可以使用appearance
實例修改默認值,是不是很炫酷。
項目需求
上面一段與本文正題無關,下面我們看一下本文的項目需求
分析
這個頁面就是一個標準的NavigationController
+ TableViewContoller
組合實現的設置頁面,導航條和Table的樣式需要訂制。
前面說到的UIApperance
協議是可以實現的,我們換一種更為普遍的方式實現,繼承。
我們繼承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;
}
}
}];
}
}];
}
}];
}