UINavigationBar的繼承與定製

咱們在iOS項目開發中,有些時候須要修改標準控件的樣式,咱們今天就圍繞一個具體項目需求,進行UINavigationBar的繼承與改造。app

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的默認背景色atom

[[UILabel appearance] setBackgroundColor:[UIColor clearColor]];

UIApperance協議就是這麼神奇,全部的UIKit控件都遵照了這個協議,全部標記了UI_APPEARANCE_SELECTOR宏的屬性,均可以使用appearance實例修改默認值,是否是很炫酷。spa

項目需求

上面一段與本文正題無關,下面咱們看一下本文的項目需求code

圖片描述

分析

這個頁面就是一個標準的NavigationController + TableViewContoller組合實現的設置頁面,導航條和Table的樣式須要訂製。orm

前面說到的UIApperance協議是能夠實現的,咱們換一種更爲廣泛的方式實現,繼承blog

咱們繼承UINavigationBar,建立子類FWBar。咱們使用storyboard實例化大致框架模型,並將NavigationViewControllerNavigationBar設置爲咱們的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方法,檢測了系統控件根據NavigationItemNavigationBar添加按鈕這個事件,而後對按鈕進行甄別,定製。

咱們找到Cancel這個按鈕,他雖然是UINavigationButton類型,可是必定是繼承了UIButton,因此咱們直接強轉成她的父類,修改其文字字體和frame。

重寫layoutSubviews這個方法,是爲了實時更新咱們的按鈕位置。這個其實也能夠不更改的,可是咱們的項目需求中,Cancel這個字段太長,字體變大之後致使了顯示不全,因此咱們將這個作按鈕的frame變大了。

注意幾點

  1. NSClassFromString(@"UINavigationButton")這個方法是咱們沒法獲取內部類的時候,獲取Class類型的方法。UINavigationButton這個類名是NSLog輸出時看到的。

  2. 這一段使用了特殊的語法糖,有興趣瞭解的參考這篇sunnyxx大神的博文,全文搜索關鍵字小括號內聯複合表達式

[subview setFrame:({
                    CGRect rect = subview.frame;
                    rect.origin.x = 8;
                    rect.size.width = 69;
                    rect;
                })];

最後的實現效果。

圖片描述

結語

截屏的效果不是太好,細心的朋友可能會發現,咱們的FWBarTableView向上滑動的過程當中會漸出陰影。

我把這段代碼分享給你們,可是這段代碼偷懶沒用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;
                            }
                        }
                    }];
                }
            }];
        }
    }];
}
相關文章
相關標籤/搜索