The UI style of iOS has been maintained for many years, but with the iteration of the app, the requirements change. In order to match the UI style, the navigation bar of many apps now also appears dynamically. Sometimes, the navigation bar and the UI below should maintain the same overall effect. Therefore, the horizontal line below the native UINavigationBar is relatively obtrusive, which is unacceptable for the development and products that pursue the ultimate user experience. Therefore, today we will study the hidden way of the horizontal line.

For general UINavigationBar, there are several solutions that can be found on the Internet at present. Method 1:

self.navigationController? .navigationBar.setBackgroundImage(UIImage.init(),for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage.init()
Copy the code

Method 2:

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

The above way, on iOS11 experiment found that the effect is navigationbar navigation are transparent state, at the bottom of the horizontal line does not appear, if add the self. The navigationController? . The navigationBar. IsTranslucent = false, set to opaque, so the navigation bar for white opaque, at the same time, a horizontal line at the bottom.

One way this before iOS11 above is no problem, but, after the upgrade iOS11 system adds some new features, first is the self. The navigationController? . The navigationBar. PrefersLargeTitles = true support headlines mode, followed by self. The navigationItem. SearchController integrates searchController properties, With preferred titles, the navigation bar is black. Swipe back to normal and return to normal. There are special cases on iOS11.

Two in no way take self. NavigationItem. SearchController situation, also can achieve the effect, relative to the benefits of a is translucent effects in the navigation bar. But added the self. The navigationItem. SearchController effects, found on that page two methods have failed. After layer location found, move position 1 pixel to the bottom of the line, no longer on the navigationbar, but on the private _UINavigationControllerPaletteClippingView layer, so in this case, we take special processing, My idea is similar to method 2, which is traversal. The code is as follows:

Extension UINavigationController {@available(iOS 11.0, *) public funcsetPaletteClippingViewLineHidden(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? {
        ifThe isKind (of: UIImageView classForCoder () && the bounds. The size, height < = 1.0 {return view as? UIImageView
        }
        
        for subView in view.subviews {
            return seekLineImageView(view: subView)
        }
        
        return nil
    }
}
Copy the code

I thought that would solve the problem, but it turns out that it doesn’t solve the problem on that page, so what’s the problem? Trace it out, and it turns out that in the viewDidLoad and viewWillAppear methods on UIViewController, _UINavigationControllerPaletteClippingView view’s subViews is empty, and then viewDidAppear method to find the child within the view, that is to say the above methods need to viewDidAppear method is valid, Invalid in other methods.

To sum up: 1, before iOS11, using mode one and two is no problem; Setting prefersLargeTitles to black the navigationbar when adding isTranslucent = false to prefersLargeTitles after iOS11 3, iOS11 after setting the self. The navigationItem. After searchController, the bottom line is transferred to _UINavigationControllerPaletteClippingView, need special treatment at this time (see above).

Finally, found on the Internet for hidden _UINavigationControllerPaletteClippingView bottom line, have another a hack way, see the HTML code is as follows:

Available (iOS 11.0, *) private funcconfigLineView() {
        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
            }
            
            letwkSelf = 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)
    }
Copy the code

It is equivalent to adding a listening event in the MainRunLoop, triggering the block callback when the beforeWaiting event is triggered, and then extracting the wkSelf internally and processing it. This is a way to delay processing, but the way to use Pointers in Swift is still complicated, so it is not recommended.