iOS解决UINavigationbar下面的横线问题

UINavigationBar下方有一个1像素高的横线,这个iOS的UI风格是保持了很多年的,但是随着app的迭代,需求是变化,为了配合UI的风格,现在有非常多的app的导航栏也是动态的隐现了,有时导航栏和下方的UI要保持一个整体效果,那么原生UINavigationBar下方的横线就比较的突兀了,对于追求极致用户体验的开发和产品来说,这是不可接受的,所以今天就来研究一下横线的隐藏方式的。

对于一般的UINavigationBar,目前网上可以查到的解决方案有几种,
方式一:

self.navigationController?.navigationBar.setBackgroundImage(UIImage.init(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage.init()
复制代码

方式二:

extension UINavigationBar {
    public func setLineViewHidden(hidden: Bool) {
        if let shadowImg = seekLineImageView(view: self) {
            shadowImg.isHidden = hidden
        }
    }
    
    private func seekLineImageView(view: UIView) -> UIImageView? {
        if view.isKind(of: UIImageView.classForCoder()) && view.bounds.size.height <= 1.0 {
            return view as? UIImageView
        }
        
        for subView in view.subviews {
            return seekLineImageView(view: subView)
        }
        return nil
    }
}
复制代码

上面方式一,在iOS11上实验发现,其效果是navigationbar导航栏呈透明状态,底部横线没有出现,如果添加上self.navigationController?.navigationBar.isTranslucent = false,设置为不透明,那么导航栏为白色不透明,同时底部横线也没有出现。

上面方案一这在iOS11之前是没有问题的,但是升级iOS11之后,系统增加了一些新特性,首先是self.navigationController?.navigationBar.prefersLargeTitles = true支持大标题模式,其次是self.navigationItem.searchController集成了searchController属性,在iOS11上进行方式一的设置,如果没有设置大标题模式也可以正常显示,但是添加了prefersLargeTitles特性后,该页面导航栏就变成黑色的了,往上滑动回到一般导航模式又变回正常,可见iOS11上有特殊情况的处理。

方式二在没有采取self.navigationItem.searchController的情况下,也是可以达到效果的,相对于方式一的好处是导航栏的半透明特效还保留着。但是添加了self.navigationItem.searchController特效之后,发现在该页面上上面两个方法都失效了。经过图层定位发现,1像素的底部线挪位置了,不再navigationbar上了,而是在_UINavigationControllerPaletteClippingView这个私有图层上了,所以这种情况下,我们采取特殊处理,我的想法和方式二类似,采取遍历的方式,代码如下:

extension UINavigationController {    
    @available(iOS 11.0, *)
    public func setPaletteClippingViewLineHidden(hidden: Bool) -> Bool {
        var result = false
        for subView in self.view.subviews {
            if let cls = NSClassFromString("_UINavigationControllerPaletteClippingView"),subView.isKind(of: cls)  {
                if let shadowImg = seekLineImageView(view: subView) {
                    shadowImg.isHidden = hidden
                    result = true
                }
            }
        }
        return result
    }
    
    private func seekLineImageView(view: UIView) -> UIImageView? {
        if view.isKind(of: UIImageView.classForCoder()) && view.bounds.size.height <= 1.0 {
            return view as? UIImageView
        }
        
        for subView in view.subviews {
            return seekLineImageView(view: subView)
        }
        
        return nil
    }
}
复制代码

原以为这样就可以解决问题了,实验结果发现,并没有解决该页面下的问题,那么问题出在哪里呢,跟踪发现,在UIViewController的viewDidLoad和viewWillAppear方法中,_UINavigationControllerPaletteClippingView视图的subViews是空的,最后再viewDidAppear方法内才找到子视图,就是说上面的方法需要放到viewDidAppear方法内才有效,其他方法中无效。

总结一下:
1、在iOS11之前,采用方式一和二都是没问题的;
2、在iOS11之后,设置prefersLargeTitles特性时,方式一添加isTranslucent = false会导致navigationbar变成黑色,没有添加isTranslucent = false则是透明导航栏;
3、iOS11之后设置self.navigationItem.searchController后,底部线位置转移到_UINavigationControllerPaletteClippingView上,此时需要特殊处理(见上文介绍)。

最后,在网上发现对于隐藏_UINavigationControllerPaletteClippingView底部线,有另外一种比较hack的方式,见代码如下:

@available(iOS 11.0, *)
    private func configLineView() {
        let unManaged = Unmanaged<ViewController>.passRetained(self).toOpaque()
        let unsafeRawPointe = UnsafeMutableRawPointer(unManaged)
        var context = CFRunLoopObserverContext(version: 0, info: unsafeRawPointe , retain: nil, release: nil, copyDescription: nil)
        self.observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.beforeWaiting.rawValue, true, 0, { (observer, activity, info) in
            
            if info == nil {
                return
            }
            
            let wkSelf = Unmanaged<ViewController>.fromOpaque(info!).takeUnretainedValue()
            if (wkSelf.navigationController?.setPaletteClippingViewLineHidden(hidden: true))! {
                
            } else {
                CFRunLoopRemoveObserver(CFRunLoopGetMain(), wkSelf.observerRef, CFRunLoopMode.commonModes)
            }
        }, &context)
        
        CFRunLoopAddObserver(CFRunLoopGetMain(), self.observerRef, CFRunLoopMode.commonModes)
    }
复制代码

实验发现,上面的方法也是可以解决问题的,其利用了RunLoop的知识,相当于在MainRunLoop中添加一个监听事件,在beforeWaiting事件时触发该block回调,内部再提取该wkSelf,再对其进行处理,相当于一种延迟处理的方式吧,但是在swift中使用指针的方式目前还是比较复杂,所以不是很推荐该方式。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值