iOS导航栏遮挡问题的总结

一.背景:在做开发的时候经常不经意就出现导航栏遮挡主视图的情况,之前出现这种情况我会先看看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.Height),所以使用父视图的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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值