11.0导航栏 ios

2017-11-13 18:21:14 idlehand 阅读数 3049

在iOS 11项目中,导航栏需透明,tableView在导航栏下方:

//解决导航栏不遮挡View

        if (@available(iOS 11.0, *)) {

            _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;

        } else {

            self.automaticallyAdjustsScrollViewInsets = NO;

        }


在滑动tableView之后, 导航栏逐渐显示,同时section在导航栏下方;

如果不处理section的位置, section会在屏幕顶部停留并被导航栏挡住一部分;

//该方法是当scrollView滑动时触发,因为UITableView继承自UIScrollView因此在这里也可以调用

    CGFloat header = 245;//这个header其实是section1 的header到顶部的距离(一般为: tableHeaderView的高度)

    if (scrollView.contentOffset.y < (header - NAVHEIGHT_64) && scrollView.contentOffset.y >= 0) {

        //当视图滑动的距离小于header时

        scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);

    }else if(scrollView.contentOffset.y >= (header - NAVHEIGHT_64))

    {

        //当视图滑动的距离大于header时,这里就可以设置section1的header的位置啦,设置的时候要考虑到导航栏的透明对滚动视图的影响

        scrollView.contentInset = UIEdgeInsetsMake(NAVHEIGHT_64, 0, 0, 0);

    }

完美解决!
2017-10-13 14:41:49 sinat_29998157 阅读数 5844

if (@available(iOS11.0, *)) {

        self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;

    } else {

        self.automaticallyAdjustsScrollViewInsets =NO;

    }

同理tableView一样处理,具体原因自行查这里不说,只说解决方案

2018-06-11 10:56:59 DreamcoffeeZS 阅读数 738

目录:

一、设置导航栏样式
二、自定义导航栏返回按钮后侧滑不可用问题
三、隐藏导航栏底部的分割线
四、导航栏引起的布局问题

相关文章:iOS状态栏的使用总结

一、设置导航栏样式

设置导航栏的样式可分为全局设置与局部设置;

1.全局设置

全局设置一般的都是在AppDelegate中设置,这样整个app都会生效,相关的代码与效果图如下:

//1.设置导航栏背景颜色
[[UINavigationBar appearance] setBarTintColor:[UIColor orangeColor]];

//2.设置导航栏背景图片
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"navigationBarImg"] forBarMetrics:UIBarMetricsDefault];

//3.设置导航栏标题样式
[[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
                                                           [UIColor purpleColor], NSForegroundColorAttributeName,
                                                           [UIFont boldSystemFontOfSize:25], NSFontAttributeName, nil]];

//4.设置导航栏返回按钮的颜色
[[UINavigationBar appearance] setTintColor:[UIColor greenColor]];

//5.设置导航栏隐藏
[[UINavigationBar appearance] setHidden:YES];

设置导航栏样式效果图

2.局部设置:

全局设置后,如果只有其中几个页面导航栏样式不同,那么我们可以使用局部设置。
注意1:局部设置与全局设置方法相同,但调用方法的对象变成了”self.navigationController.navigationBar”

注意2:局部设置必须遵循一个原则:”进入页面时修改,离开页面时还原”。
比如我们进入一个页面,需要设置当前导航栏的背景色为灰色,使用如下方法:

//进入页面时设置颜色:灰色
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [self.navigationController.navigationBar setBarTintColor:[UIColor grayColor]];
}

//离开页面时还原为全局设置:橙色
- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [self.navigationController.navigationBar setBarTintColor:[UIColor orangeColor]];
}

二、解决自定义导航栏返回按钮后侧滑不可用问题

iOS导航栏自带的返回按钮形式单一,所以大多情况下,我们都需要自定义导航栏返回按钮。但是此时我们却发现页面的侧滑返回功能不可用了。为了解决这个问题,我们需要在App中使用我们自定义的导航控制控制器,示例代码如下:

#import “BaseNavigationController.h"
//第一步:设置自定义导航控制器使用UIGestureRecognizerDelegate
@interface BaseNavigationController ()<UIGestureRecognizerDelegate>
@end

@implementation BaseNavigationController
- (void)viewDidLoad {
    [super viewDidLoad];
    //第二步:设置自定义导航控制器的侧滑手势的代理
    self.interactivePopGestureRecognizer.delegate = self;
}


//第三步:实现代理方法
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if (self.childViewControllers.count == 1) {
        // 表示用户在根控制器界面,就不需要触发滑动手势,
        return NO;
    }
    return YES;
}
@end

三、隐藏导航栏底部的分割线

隐藏导航底部分割线也是我们偶尔会遇到的开发需求,首先我们可以通过Xcode的Debug View Hierarchy功能查看导航栏的视图结构,效果如下:

导航栏视图层级图

从图中可以看出,导航栏的底部分割线是一个UIImageView对象,而且高度只有0.5,所以我们可以据此获取到导航栏的底部分割线对象,在一个视图控制器中实现此需求,代码如下:

#import "TestViewController.h"
@interface TestViewController ()
//第一步:设置一个属性,存放导航栏底部分割线对象
@property (nonatomic, strong) UIImageView *navBarBottomImage;
@end

@implementation TestViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    ////第三步:获取导航栏底部分割线对象
    UIImageView *navBarBottomImage = [self findNavBarBottomImage:self.navigationController.navigationBar];
    self.navBarBottomImage = navBarBottomImage;
 }

//第四步:设置分割线的显示或隐藏
//进入页面隐藏分割线
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
        self.navBarBottomImage.hidden = YES;
}
//离开页面时显示分割线
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    self.navBarBottomImage.hidden = NO;
}

//第二步:添加用于获取导航栏分割线的方法
//导航栏底部分割线是一个UIImageView,且高度不超过1.0个高度,可据此查找此对象
-(UIImageView *)findNavBarBottomImage:(UIView *)view {
    if ([view isKindOfClass:UIImageView.class] && view.bounds.size.height <= 1.0) {
        return (UIImageView *)view;
    }
    for (UIView *subview in view.subviews) {
        UIImageView *imageView = [self findNavBarBottomImage:subview];
        if (imageView) {
            return imageView;
        }
    }
    return nil;
}

四、导航栏引起的布局问题

1.内容偏移属性:automaticallyAdjustsScrollViewInsets

automaticallyAdjustsScrollViewInsets是视图控制器的一个属性,默认为YES,用于优化滑动类视图(继承于UIScrollView的视图)在视图控制里的显示:

iOS系统的导航栏UINavigationBar与标签栏UITabBar默认都是半透明模糊效果,在这种情况下系统会对视图控制器的UI布局进行优化:视图控制器里面第一个被添加进去的视图是滑动类视图,并且其Frame是整个屏幕大小时,系统会自动调整其contenInset,以保证滑动视图里的内容不被UINavigationBar与UITabBar遮挡

但是对于普通的视图,此时我们仍然需要注意:非滑动视图的布局仍然要考虑导航栏和标签栏高度,注意不被遮挡,比如布局的时候加上导航栏高度,以免内容被导航栏遮挡。

我们可以通过一段代码来测试一下效果,在默认导航栏(半透明)的视图控制器里添加如下代码:

//UITextView是滑动视图,内容自动向下偏移,不会被导航栏覆盖
UITextView *leftTextView = [[UITextView alloc] init];
leftTextView.frame = CGRectMake(0, 0,100, kDeviceHeight); //
leftTextView.backgroundColor = [UIColor lightGrayColor];
leftTextView.text = @"君不见,黄河之水天上来,奔流到海不复回。君不见,高堂明镜悲白发,朝如青丝暮成雪。人生得意须尽欢,莫使金樽空对月。天生我材必有用,千金散尽还复来。";
leftTextView.font = [UIFont systemFontOfSize:18];
leftTextView.editable = NO;
[self.view addSubview:leftTextView];

//UIView是非滑动视图,内容被导航栏部分覆盖
UIView *rightView= [[UIView alloc] initWithFrame:CGRectMake(150, 0, 100, 100)];
rightView.backgroundColor = [UIColor redColor];
[self.view addSubview:rightView];

导航栏透明情况下,滑动视图自动偏移,普通视图被遮挡

其实,这种系统的优化也是可以控制关闭的,关闭优化之后,滑动视图就会和普通视图一样,如果还设置其布局的原点是(0,0),其内容就会被导航栏所覆盖,关键代码如下:

//automaticallyAdjustsScrollViewInsets在11.0后失效,所以需要判断
if (@available(iOS 11.0,*)) {
       scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}else{
       //automaticallyAdjustsScrollViewIn,关闭自动偏移的系统优化
       self.automaticallyAdjustsScrollViewInsets = NO;
}

2.边缘延伸属性:edgesForExtendedLayout

edgesForExtendedLayout也是视图控制器的布局属性,默认值是UIRectEdgeAll,即:当前视图控制器里各种UI控件会忽略导航栏和标签的存在,布局时若设置其原点设置为(0,0),视图会延伸显示到导航栏的下面被覆盖。

所以我们可以设置self.edgesForExtendedLayout=UIRectEdgeNone,此时视图控制器里内容就会避开导航栏和标签栏了,依然是上面的leftTextView和rightView,设置了UIRectEdgeNone之后的效果图如下:

self.edgesForExtendedLayout=UIRectEdgeNone

3.导航栏透明属性translucent

上述两种属性都是在解决导航栏半透明情况下的布局问题,但是如果我们的需求就是导航栏不透明,那么视图控制器里的控件就会默认从(0,64)开始布局了,设置导航栏不透明的方法如下:

self.navigationController.navigationBar.translucent= NO;
2017-08-27 16:21:34 chokshen 阅读数 5593

一.背景:在做开发的时候经常不经意就出现导航栏遮挡主视图的情况,之前出现这种情况我会先看看View UI Hierarchy,然后把视图的坐标手动调整,例如把视图的y坐标写成64(建议封装成宏,因为有刘海的手机状态栏和导航栏的高度之和不是64),即减去状态栏和导航栏的高度之和,这个方法是可行的,但是不适用所有的场景。不同的场景应该采用不同的解决方案,要想找到最合适的解决方案,就必须去研究其中的原理。

二.基础知识

1.关于坐标系

iOS7之前的坐标系见图一,坐标原点是从导航栏下方开始计算的,iOS7以后的坐标系是铺满全屏幕的(图二),也就是将屏幕的左上角作为坐标原点。

                         

                                   图一                                                                                                              图二

2.edgesForExtendedLayout属性

前面说到了iOS7的坐标系是铺满全屏幕的,它实际上是通过edgesForExtendedLayout属性来实现的,该属性默认的值是UIRectEdgeAll,该属性的取值见下面:

@available(iOS 7.0, *)
public struct UIRectEdge : OptionSet {

    public static var top: UIRectEdge { get } //从屏幕的左上角开始计算,但不覆盖tabbar标签栏

    public static var left: UIRectEdge { get }

    public static var bottom: UIRectEdge { get }//从导航栏下方开始计算,覆盖tabbar标签栏

    public static var right: UIRectEdge { get }

    public static var all: UIRectEdge { get } //默认值,铺满全屏幕
}

ps:它还可以设置为UIRectEdgeNone,即和iOS7以前的坐标系一样,swift3.0以后没有这个成员变量,应该直接赋值为[]

注意:edgesForExtendedLayout属性只是改变了坐标系原点的位置,并不会改变view的宽度和高度,也就是说无论该属性被设置成哪一个值,控制器父视图的默认frame都是 (0.0,0.0,Screen.Width,Screen.Heiht),所以使用父视图的frame属性给其他子视图时要注意。

3.automaticallyAdjustsScrollViewInsets属性

先来看看官方文档怎么说,automaticallyAdjustsScrollViewInsets根据按所在界面的status barnavigationbar,与tabbar的高度,自动调整scrollviewcontentInset,设置为No,不让viewController调整,我们自己修改布局即可~。该属性是针对scrollview及其子类的,例如tableViewcollectionView,但是该属性只对控制器视图层级中第一个scrollview及其子类起作用,如果视图层级中存在多个scrollview及其子类,官方建议该属性设置为No,此时应该手动设置它的contentInset

ps:当你发现tableview莫名其妙地向下偏移导航栏的高度时,就是这个属性在作怪,将其设置为No即可

更新:iOS 11以后该属性被废弃,取而代之的是UIScrollViewcontentInsetAdjustmentBehavior。当tableView超出安全区域时系统自动调整了SafeAreaInsets值,进而影响adjustedContentInset值,在iOS 11中决定tableView的内容与边缘距离的是adjustedContentInset属性,而不是contentInset。因为系统对adjustedContentInset值进行了调整,所以导致tableView的内容到边缘的距离发生了变化,导致tableView下移了20pt(statusbar高度)或64pt(navigationbar高度)。

4.contentInsetAdjustmentBehavior属性

首先来介绍SafeAreaInsets的概念

如果你的APP中使用的是自定义的navigationbar,隐藏掉系统的navigationbar,并且tableView的frame为(0,0,SCREEN_WIDTH, SCREEN_HEIGHT)开始,那么系统会自动调整SafeAreaInsets值为(20,0,0,0),如果使用了系统的navigationbar,那么SafeAreaInsets值为(64,0,0,0),如果也使用了系统的tabbar,那么SafeAreaInsets值为(64,0,49,0)。

    @available(iOS 11.0, *)
    public enum ContentInsetAdjustmentBehavior : Int {

        // 如果scrollview在一个automaticallyAdjustsScrollViewInsets = YES的controller上,并且这个Controller包含在一个navigation controller中,这种情况下会设置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滚动。
        //其他情况下与scrollableAxes相同
        case automatic 

        // 在可滚动方向上adjustedContentInset = safeAreaInset + contentInset,在不可滚动方向adjustedContentInset = contentInset;
        // 依赖于scrollEnabled和alwaysBounceHorizontal / vertical = YES,scrollEnabled默认为yes
        // 所以大多数情况下,计算方式还是adjustedContentInset = safeAreaInset + contentInset
        case scrollableAxes 

        // adjustedContentInset = contentInset
        case never 
 
        // adjustedContentInset = safeAreaInset + contentInset
        case always 
    }

所以iOS11以后可以使用contentInsetAdjustmentBehavior = .never来代替automaticallyAdjustsScrollViewInsets = No。

三.例子

1.edgesForExtendedLayoutautomaticallyAdjustsScrollViewInsets(contentInsetAdjustmentBehavior)均为默认值,左边是普通的UIView,右边包含TableView

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.backgroundColor=UIColor.blue
        print(tableView.frame)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    
    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        if cell == nil {
            cell=UITableViewCell(style: .default, reuseIdentifier: "cell")
        }
        cell?.textLabel?.text="测试"
        return cell!
    }

        左边:                                                                                    右边:
                   

ps:虽然tableview的内容没有被遮挡,但是它还是铺满全屏幕的,从导航栏的透明度就可以看出来,下面是它的视图层级

 

 

2.edgesForExtendedLayout均为UIRectEdgeNone,左边是普通的UIView,右边包含TableView

       

ps:这里因为没有tabbar,所以下面是铺满的

 

3.edgesForExtendedLayout为默认,iOS11以前automaticallyAdjustsScrollViewInsetsNo(iOS11以后contentInsetAdjustmentBehavior为never),只看TableView

                                 

4.edgesForExtendedLayoutUIRectEdgeNone,iOS11以前automaticallyAdjustsScrollViewInsetsNo(iOS11以后contentInsetAdjustmentBehavior为never),只看TableView

                          

ps:当tableview的frame在导航栏下方时,automaticallyAdjustsScrollViewInsets属性就没有什么意义

5.多个tableview,edgesForExtendedLayoutautomaticallyAdjustsScrollViewInsets均为默认值

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableviewVC = TestTableViewController()
        let tableviewVC1 = Test1TableViewController()
        tableviewVC.tableView.frame=CGRect(x: 0, y: 0, width: 100, height: view.frame.height)
        tableviewVC1.tableView.frame=CGRect(x: 100, y: 0, width: UIScreen.main.bounds.width-100, height: view.frame.height)
        view.addSubview(tableviewVC.tableView)
        view.addSubview(tableviewVC1.tableView)
        self.addChildViewController(tableviewVC)
        self.addChildViewController(tableviewVC1)
        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


                          

ps:automaticallyAdjustsScrollViewInsets属性失效,两个tableview视图均被遮挡,这个地方有点疑惑,我之前做项目的时候第一个tableview是没被遮挡的,也就是对第一个tableview还是有效的。不管怎样,按照官方文档的说法,当有多个scrollview及其子类的时候,还是应该把该属性设置为No,手动确定它们的位置。上面这种情形的解决方案只需两句代码就搞定:

        //iOS11以前解决方案
        self.automaticallyAdjustsScrollViewInsets=false //iOS11以后 tableView.contentInsetAdjustmentBehavior = .never
        self.edgesForExtendedLayout=[] //如果下面还有tabbar,则应该设置为self.edgesForExtendedLayout=UIRectEdge.bottom,否则tabbar会出现黑条

  

                          

参考资料:https://www.jianshu.com/p/efbc8619d56b

2017-10-19 12:01:45 iOS_PING 阅读数 5931

部分总结;


(一)导航栏高度的变化

(1) iOS11之前导航栏默认高度为44pt(这里高度指NavigationBar);

(2) iOS11之后如果设置了大标题样式, 则为96pt,默认情况下还是44pt;

(3) 但在iPhoneXstatusBar20pt变成了44pt,所以iPhoneX上高度(statusBar + NavigationBar)变为88pt,如果项目里隐藏了导航栏加了自定义按钮之类的,这里需要注意适配一下。


if (@available(iOS 11.0, *)) {

            //iOS11之后

            

            //导航大标题, 上滑到顶部时动态切换大小标题样式 (导航栏高度UINavigationBar = 44/96)

            self.navigationController.navigationBar.prefersLargeTitles = YES;

            //自动模式,依赖于上一个item的设置; 上一个item设置为自动并且当前导航栏prefersLargeTitles=YES,则显示大标题样式;

            self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic;

//            //prefersLargeTitles=YES,滚动到顶部时,当前总是显示大标题样式

//            self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways;

//            //prefersLargeTitles=YES,滚动到顶部时,当前也总不会显示大标题样式

//            self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;

            

            //标题样式变化时, btnframe不变

            UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"设置" style:UIBarButtonItemStylePlain target:self action:@selector(testDidClick)];

            self.navigationItem.rightBarButtonItem = item;

            

            //标题样式变化时, btnframe不变

            UIBarButtonItem *item1 = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(testDidClick)];

            self.navigationItem.leftBarButtonItem = item1;

            

            //titleView

            

        } else {

            // Fallback on earlier versions

            

            //titleView


            

        }



(二) 导航栏图层及对titleView布局的影响

iOS11之后titleView层级发生了变化, 具体如图展示:



iOS 11 导航栏变化

阅读数 1429