UINavigationController結(jié)構(gòu)分析以及使用

一. UINavigationController的結(jié)構(gòu)

UINavigationController的結(jié)構(gòu)大概分為如下三個(gè)區(qū)域:

對應(yīng)的更加立體的結(jié)構(gòu)如下圖:


其中UILayoutContainerView對應(yīng)的是self.navigationController.view ,我們可以打個(gè)斷點(diǎn)來驗(yàn)證一下:

UINavigationTransitionView轉(zhuǎn)場的視圖,也就是我們說的內(nèi)容區(qū)
UINavigationBar即導(dǎo)航欄
其中ToolBar默認(rèn)是隱藏的 我們可以手動(dòng)顯示 不過我們一般很少用到它:

self.navigationController.toolbarHidden = NO;

二. UINavigationTransitionView 內(nèi)容區(qū)

在iOS 7.0之前我們的導(dǎo)航欄是擬物化風(fēng)格的,導(dǎo)航條是不透明的,內(nèi)容區(qū)是在導(dǎo)航欄下緊挨著的(Y值從64開始)
但是從iOS 7.0以后 我們的導(dǎo)航欄變成了扁平化風(fēng)格即纲,導(dǎo)航欄是透明的了,也就是說ViewController默認(rèn)使用全屏布局

為了更好的過渡,蘋果從iOS 7.0以后新增了幾個(gè)屬性 我們一一為大家講解

1. edgesForExtendedLayout

edgesForExtendedLayout是一個(gè)類型為UIRectEdge的屬性县忌,可以指定邊緣要延伸的方向怀读。因?yàn)閕OS7之后鼓勵(lì)全屏布局绸栅,它的默認(rèn)值是UIRectEdgeAll劲够,四周邊緣均延伸职车,就是說如果即使視圖中上有navigationBar,下有tabBar峦失,那么視圖仍會(huì)延伸覆蓋到四周的區(qū)域。

效果一:導(dǎo)航欄透明 并且內(nèi)容全屏布局
先來看一段非常普通的代碼

- (void)viewDidLoad {
   [super viewDidLoad];
   UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
   redView.backgroundColor = [UIColor redColor];
   [self.view addSubview:redView];    
}

運(yùn)行效果如下圖:



可以看到紅色的View的bounds是從屏幕左上角開始的,而不是導(dǎo)航欄左下方.

其實(shí)等價(jià)于

    //導(dǎo)航欄透明 并且使用全屏布局
    self.navigationController.navigationBar.translucent = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;
    ....

效果二:導(dǎo)航欄透明 非全屏布局
那么如果此時(shí)我們想讓redView的bounds從導(dǎo)航欄的左下方開始該如何操作呢?其實(shí)我們只需要改動(dòng)一句代碼即可:

//不讓View延展到整個(gè)屏幕
self.edgesForExtendedLayout = UIRectEdgeNone;

效果圖如下:


效果三:導(dǎo)航欄不透明 非全屏布局

如果我們將導(dǎo)航欄設(shè)置為不透明效果會(huì)如何呢?

- (void)viewDidLoad {
    [super viewDidLoad];   
    self.navigationController.navigationBar.translucent = NO;
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
}


可以看到我們將navigationBar.translucent設(shè)置為NO之后 控制器自動(dòng)變?yōu)榉侨敛季至耸趼穑簿褪堑葍r(jià)于

self.edgesForExtendedLayout = UIRectEdgeNone;

效果四:導(dǎo)航欄不透明 全屏布局

如果導(dǎo)航欄不透明但是又要實(shí)現(xiàn)全屏布局的效果 該如何操作呢?
此時(shí)只需要將extendedLayoutIncludesOpaqueBars設(shè)置為YES即可:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationController.navigationBar.translucent = NO;
    self.extendedLayoutIncludesOpaqueBars = YES;
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
}

2. extendedLayoutIncludesOpaqueBars

首先我們來看看官方對該屬性的定義:

// Defaults to NO, but bars are translucent by default on 7_0.
@property(nonatomic,assign) BOOL extendedLayoutIncludesOpaqueBars NS_AVAILABLE_IOS(7_0); 

擴(kuò)展布局是否包括不透明的Bars尉辑,默認(rèn)為NO

蘋果這樣做其實(shí)是很人性化的,如果bars不透明的情況下较屿,再使擴(kuò)展布局到bars的下方隧魄,這樣感覺是毫無意義的,所以在bars不透明的情況下隘蝎,默認(rèn)不會(huì)延伸布局购啄。

3. automaticallyAdjustsScrollViewInsets

從導(dǎo)航視圖Push進(jìn)來的以ScrollView 為主的視圖,本來我們的cell是放在(0,0)的位置上的嘱么,但是考慮到導(dǎo)航欄狮含、狀態(tài)欄會(huì)擋住后面的主視圖,所以系統(tǒng)會(huì)自動(dòng)把我們的內(nèi)容向下偏移64px(下方位置如果是tarbar則向上偏移49px

我們以tableView為例子來驗(yàn)證一下,如下圖:

可以看到默認(rèn)情況下Cell的顯示是從導(dǎo)航欄下方開始的,我們可以打印一下tableView的信息查看一下,如下圖:

可以看到默認(rèn)情況下系統(tǒng)將contentOffset下移了64

那么曼振,當(dāng)我們不想讓系統(tǒng)自動(dòng)為我們下移時(shí)我們可以這樣設(shè)置:

if (@available(iOS 11.0, *)) {
    self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
    self.automaticallyAdjustsScrollViewInsets = NO;
}

3. 導(dǎo)航控制器通過棧來管理子控制器

UINavigationController 是通過棧的形式來管理控制器的,如何來驗(yàn)證這一點(diǎn)呢? 我們新建四個(gè)控制器,然后從第一個(gè)控制器依次 push到最后一個(gè)控制器 此時(shí)我們打印 po self.navigationController.viewControllers 如圖:

當(dāng)我們打印當(dāng)前可視控制器的時(shí)候,顯示的就是棧頂?shù)目刂破?如圖:

當(dāng)然我們從pushViewController:animated:popViewController:animated:這兩個(gè)方法的描述也可以看出來導(dǎo)航欄的運(yùn)作原理.

三. UINavigationBar 導(dǎo)航欄區(qū)域

1. UINavigationBar

UINavigationBar繼承自 UIView , 它主要用來管理導(dǎo)航欄的items的,我們可以看到它里面有一個(gè)items的數(shù)組:

@property(nullable,nonatomic,copy) NSArray<UINavigationItem *> *items;

UINavigationController一樣,它也是通過棧來管理items的,而且和self.navigationController.viewControllers是一一對應(yīng)的:

如下圖:

2. UINavigationItem

UINavigationItem繼承自NSObject 而不是UIView几迄,所以他是一個(gè)模型而不是一個(gè)視圖,我們可以使用self.navigationItem來獲取當(dāng)前頁面導(dǎo)航欄上顯示的全部信息冰评,包括title映胁、titleView 、leftBarButtonItem甲雅、rightBarButtonItem解孙、backBarButonItem

因?yàn)樗且粋€(gè)模型所以當(dāng)導(dǎo)航欄 push一個(gè)控制器的時(shí)候,他是時(shí)時(shí)刷新變化的务荆,展示的是當(dāng)前控制器的Item信息。 如果我們想讓導(dǎo)航欄有一個(gè)固定不變的控件的話 我們可以向 UINavigationBar中添加一個(gè)子控件即可:

UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 30)];
    redView.backgroundColor = [UIColor redColor];
    [self.navigationController.navigationBar addSubview:redView];

這樣每個(gè)頁面都包含了這個(gè)控件



3. 導(dǎo)航欄透明效果

比較暴力的方式:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
    //處理導(dǎo)航欄有條線的問題
    [self.navigationController.navigationBar setShadowImage:[UIImage new]];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
    [self.navigationController.navigationBar setShadowImage:nil];
}

當(dāng)然我們也可以逐個(gè)去遍歷navigationBar的子視圖,然后改變他們的透明度

// 設(shè)置導(dǎo)航欄背景透明度
- (void)setNavigationBackgroundAlpha:(CGFloat)alpha {
    
    if (!self.navigationController.navigationBar.isTranslucent) return;
    
    // 導(dǎo)航欄背景透明度設(shè)置
    UIView *barBackgroundView;// _UIBarBackground
    UIImageView *backgroundImageView;// UIImageView
    UIView *backgroundEffectView;// UIVisualEffectView
    
    if (@available(iOS 10.0, *)) {//
        barBackgroundView = [self.navigationController.navigationBar.subviews objectAtIndex:0];//_UIBarBackground
        backgroundImageView = [barBackgroundView.subviews objectAtIndex:0];//UIImageView
        if (backgroundImageView != nil && backgroundImageView.image != nil) {
            barBackgroundView.alpha = alpha;
        } else {
            backgroundEffectView = [barBackgroundView.subviews objectAtIndex:1];//backgroundEffectView
            if (backgroundEffectView != nil) {
                backgroundEffectView.alpha = alpha;
            }
        }
        
    }else{
        for (UIView *view in self.navigationController.navigationBar.subviews) {
            if ([view isKindOfClass:NSClassFromString(@"_UINavigationBarBackground")]) {
                barBackgroundView = view;
                barBackgroundView.alpha = alpha;
                break;
            }
        }
        for (UIView *otherView in barBackgroundView.subviews) {
            if ([otherView isKindOfClass:NSClassFromString(@"UIImageView")]) {
                backgroundImageView = (UIImageView *)otherView;
                backgroundImageView.alpha = alpha;
            }else if ([otherView isKindOfClass:NSClassFromString(@"_UIBackdropView")]) {
                backgroundEffectView = otherView;
                backgroundEffectView.alpha = alpha;
            }
        }
    }
    
    // 對導(dǎo)航欄下面那條線做處理
    self.navigationController.navigationBar.clipsToBounds = alpha == 0.0;
}

在使用的時(shí)候我們可以:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self setNavigationBackgroundAlpha:0.0];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self setNavigationBackgroundAlpha:1.0];
}

如果想在透明導(dǎo)航欄間平滑的切換 可以參考GitHubiOS透明導(dǎo)航欄的平滑過渡(進(jìn)階版)

4. 導(dǎo)航欄隱藏

在項(xiàng)目中經(jīng)常碰到首頁頂部是無限輪播,需要靠最上面顯示.然后push到下一個(gè)頁面的時(shí)候是需要導(dǎo)航欄的

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];
    [self.navigationController setNavigationBarHidden:YES animated:animated];
    
}
-(void)viewWillDisappear:(BOOL)animated
{
    self.navigationController.navigationBarHidden = NO;
    [super viewWillDisappear:animated];
}

5. UIBarButtonItem設(shè)置間距


如上圖 ,當(dāng)導(dǎo)航條上有多個(gè)Item的時(shí)候穷遂,如果我們想調(diào)節(jié)兩個(gè)Item之間的距離函匕,讓他們的距離更長該如何操作呢?其實(shí)我們只需要添加一個(gè)UIBarButtonSystemItemFixedSpace樣式的Item即可:

    UIBarButtonItem *helpBtn = [[UIBarButtonItem alloc] initWithTitle:@"幫助" style:UIBarButtonItemStylePlain target:self action:nil];
    UIBarButtonItem *flexBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil];
    flexBtn.width = 45;
    UIBarButtonItem *moreBtn = [[UIBarButtonItem alloc] initWithTitle:@"更多" style:UIBarButtonItemStylePlain target:self action:nil];
    
    self.navigationItem.rightBarButtonItems = @[moreBtn,flexBtn,helpBtn];

效果圖如下:

6. 變更返回圖片

如果我們想變更導(dǎo)航條上的返回圖標(biāo):

    UIImage *backImg = [[UIImage imageNamed:@"arrow"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    self.navigationController.navigationBar.backIndicatorImage = backImg;
    self.navigationController.navigationBar.backIndicatorTransitionMaskImage = backImg;

效果如下:


此時(shí)如果我們想讓返回圖標(biāo)后面的文字消失該怎么做呢蚪黑?

 [[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault];

該方法在iOS11的時(shí)候會(huì)出現(xiàn)返回圖標(biāo)下沉效果盅惜,如下圖:


此時(shí)我們可以:

[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(-100, 0) forBarMetrics:UIBarMetricsDefault];

這樣就正常了,如圖:


7. 設(shè)置文字顏色和大小

 [navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],NSFontAttributeName:[UIFont systemFontOfSize:25]}];

8. 設(shè)置背景圖片

[navigationBar setBackgroundImage:[UIImage imageNamed:] forBarMetrics:UIBarMetricsDefault];

9. 全屏手勢滑動(dòng)

- (void)viewDidLoad {
    [super viewDidLoad];    
    //設(shè)置手勢代理
    self.interactivePopGestureRecognizer.enabled = NO;
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self.interactivePopGestureRecognizer.delegate action:NSSelectorFromString(@"handleNavigationTransition:")];
    [self.view addGestureRecognizer:pan];
    pan.delegate = self;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
    return self.viewControllers.count>1;
}

10.返回到指定控制器

方法一:

   [self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:2] animated:YES];

方法二:

 for (UIViewController *temp in self.navigationController.viewControllers) {
    if ([temp isKindOfClass:[要跳轉(zhuǎn)回的控制器 class]]) {
        [self.navigationController popToViewController:temp animated:YES];
    }
}

10.點(diǎn)擊TabBarItem直接push操作 比如跳轉(zhuǎn)到登錄界面

第一步:在自定義TabBarController中遵守 UITabBarControllerDelegate協(xié)議
第二步: self.delegate = self;
第三步:實(shí)現(xiàn) - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController方法

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
    NSLog(@"--tabbaritem.title--%@", viewController.tabBarItem.title);

    //這里我判斷的是當(dāng)前點(diǎn)擊的tabBarItem的標(biāo)題
    if ([viewController.tabBarItem.title isEqualToString:@"我的"]) {
        //如果用戶ID存在的話忌穿,說明已登陸
        if (USER_ID) {
            return YES;
        }
        else
        {
            //跳到登錄頁面
            HPLoginViewController *login = [[HPLoginViewController alloc] init];
            //隱藏tabbar
            login.hidesBottomBarWhenPushed = YES;
            [((UINavigationController *)tabBarController.selectedViewController) pushViewController:login animated:YES];
            
            return NO;
        }
    } else{
        return YES;
    }
}

本文會(huì)持續(xù)更新抒寂,包括在工作中遇到的關(guān)于導(dǎo)航條的問題我都會(huì)記錄再此......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掠剑,隨后出現(xiàn)的幾起案子屈芜,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件井佑,死亡現(xiàn)場離奇詭異属铁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躬翁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門焦蘑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盒发,你說我怎么就攤上這事例嘱。” “怎么了宁舰?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵拼卵,是天一觀的道長。 經(jīng)常有香客問我明吩,道長间学,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任印荔,我火速辦了婚禮低葫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仍律。我一直安慰自己嘿悬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布水泉。 她就那樣靜靜地躺著善涨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪草则。 梳的紋絲不亂的頭發(fā)上钢拧,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音炕横,去河邊找鬼源内。 笑死,一個(gè)胖子當(dāng)著我的面吹牛份殿,可吹牛的內(nèi)容都是我干的膜钓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼卿嘲,長吁一口氣:“原來是場噩夢啊……” “哼颂斜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拾枣,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤沃疮,失蹤者是張志新(化名)和其女友劉穎盒让,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忿磅,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糯彬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葱她。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撩扒。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吨些,靈堂內(nèi)的尸體忽然破棺而出搓谆,到底是詐尸還是另有隱情,我是刑警寧澤豪墅,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布泉手,位于F島的核電站,受9級特大地震影響偶器,放射性物質(zhì)發(fā)生泄漏斩萌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一屏轰、第九天 我趴在偏房一處隱蔽的房頂上張望颊郎。 院中可真熱鬧,春花似錦霎苗、人聲如沸姆吭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽内狸。三九已至,卻和暖如春厘擂,著一層夾襖步出監(jiān)牢的瞬間昆淡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工刽严, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昂灵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓港庄,卻偏偏與公主長得像倔既,于是被迫代替她去往敵國和親恕曲。 傳聞我的和親對象是個(gè)殘疾皇子鹏氧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 在上一篇文章Swift中的變量和常量中我總結(jié)了一些自己對于變量和常量的認(rèn)識(shí),最近學(xué)習(xí)了閉包佩谣,順便給大家分享一下關(guān)于...
    老板娘來盤一血閱讀 18,664評論 16 87
  • 一把还、閉包的介紹 閉包和OC中的block非常相似:OC中的block是匿名的函數(shù);Swift中的閉包是一個(gè)特殊的函...
    magic_pill閱讀 762評論 0 1
  • Swift 介紹 簡介 Swift 語言由蘋果公司在 2014 年推出,用來撰寫 OS X 和 iOS 應(yīng)用程序 ...
    大L君閱讀 3,201評論 3 25
  • 作為一門現(xiàn)代的高級編程語言吊履,Swift代替我們進(jìn)行了對象的創(chuàng)建和銷毀等相關(guān)的內(nèi)存管理安皱。它使用了一個(gè)優(yōu)雅的技術(shù),叫做...
    Maru閱讀 2,097評論 4 17
  • 一艇炎、質(zhì)檢的現(xiàn)狀 把呼叫中心行業(yè)按照產(chǎn)品化思路來理解酌伊,每通人工電話就是我們的產(chǎn)品。針對這個(gè)產(chǎn)品的質(zhì)量控制就是質(zhì)檢缀踪,包...
    馬崇閱讀 7,542評論 6 12