Custom NavigationBar--drawing with UIView

Although the UINavigationBar that comes with the system in iOS is powerful, it has various small problems in use, and it is very troublesome to deal with. Of course, there are also some excellent third-party NavigationBars that are also very powerful and more convenient to use, but there is still a problem that they are based on subclasses inherited from UINavigationBar, so there are also such problems as the display of modal bullet boxes. UINavigationBar is essentially a special class based on UIView that can hook to the property of UIViewController. The latter hook is difficult to do, so the former can be easily implemented based on UIView.

This article will be based on UIView, with the basic design concept of "left area, middle area, and right area" and the goal of "simple, convenient, and beautiful". Sorry for the lack of narrative.

train of thought

The most used scenarios of NavigationBar are nothing more than the title bar and the left and right buttons. Most of the left buttons exist and the right buttons are implemented according to specific business needs. This abstracts away the implementation approach used - "left zone, middle zone, right zone". That is, implement three container classes UIView to wrap child controls. Usually, the more troublesome part of writing controls is the layout. Whether it is snapkit or frame layout, it is necessary to calculate the parameters of the specific situation to lay out. If you only determine the size of the space and let the NavigationBar automatically calculate its frame, can it work? The answer is definitely yes. Because the normal NavigationBar control layout is regular rather than complex or chaotic, that is to say, as long as the specified control is placed in the specified position container, let it be centered, so that there is a frame parameter of the parent container and a size Parameters, the frame itself is also determined.

Sometimes you want to use a very simple constructor to initialize a class, especially the control class. For example, if we want to implement a NavigationBar with the back button on the left and the title text in the middle, we only want to pass in the picture of the back button, the title text in the middle, and then implement the event properties of the back button without too much extra code.

UINavigationBar has a Gaussian blur effect. If it is set properly, the effect looks very beautiful. We also hope to have this feature. Of course, with Gaussian blur, background images and colors are indispensable.

The annoying thing about UINavigationBar is a gray line at the bottom. Sometimes it takes a few extra lines to remove it, which is extremely bloated. But it is cumbersome and cannot be directly implemented. Gray lines are still helpful when the contrast is weak, so this feature also needs to be considered.

So what if there is no need for a layout like "left, center and right"? In this case, UINavigationBar is useless, but it is still necessary to consider a convenient constructor that can pass in a custom View to implement. The realization of this point has not been completed, because such a business is rare, and those who need it must use other methods to realize it.

component

Define three content containers to manage incoming subclasses. Note that there is only one subclass passed in, but the subclass can be a collection of subclasses of subclasses.

    private lazy var leftContent:      UIView = self.createLeftContent()
    private lazy var centerContent:    UIView = self.createCenterContent()
    private lazy var rightContent:     UIView = self.createRightContent()

The implementation relies on lazy loading to initialize.

initialization:

A convenience constructor is defined here to initialize the subclass control passed in by the user. The method is used setupView()to add to the container and calculate its frame.

    convenience init(leftView: UIView? = nil, centerView: UIView? = nil, rightView: UIView? = nil) {
    
    
        self.init()
        
        setupView(leftView: leftView, centerView: centerView, rightView: rightView)
    }

Convenience constructor:

Provide an additional "left button, middle title bar" method:

    convenience init(title: String, leftText: String) {
    
    
        self.init()
        self.init(leftView: self.createLeftButton(text: leftText),
                  centerView: self.createCenterLabel(text: title),
                  rightView: nil)
    }

Then provide a "left button, middle title bar, right button" method:

    convenience init(title: String, leftText: String, rightText: String) {
    
    
        self.init()
        self.init(leftView: self.createLeftButton(text: leftText),
                  centerView: self.createCenterLabel(text: title),
                  rightView: self.createRightButton(text: rightText))
    }

In this way, the user does not need to write too much initialization code when using it.

Background image/blurred image:

First implement a UIVisualEffectView object, and then define isBlurthe property to control whether to display the Gaussian blur background.

    private var _isBlur: Bool?
    /// NavigationBar whether should be presenting a blur view.
    public var isBlur: Bool? {
    
    
        set{
    
    
            _isBlur = newValue ?? false
            if _isBlur == true {
    
    
                self.effectView.isHidden = false
            } else {
    
    
                self.effectView.isHidden = true
            }
        }
        get{
    
    
            return _isBlur
        }
    }

The picture is simple, here also defines a property barBackgroundImageto accept the UIImage background image:

    private var _barBackgroundImage: UIImage?
    /// NavigationBar background image.
    public var barBackgroundImage: UIImage?{
    
    
        set{
    
    
            _barBackgroundImage = newValue ?? UIImage(named: "")
            self.backgroundColor = UIColor(patternImage: _barBackgroundImage!)
        }
        get{
    
    
            return _barBackgroundImage
        }
    }

other:

Here it is implemented isEnbaleDividerLineand isFullScreenused to decide whether to display the dividing line and whether the NavigationBar is displayed in full screen

    private var _isFullScreen: Bool?
    /// NavigationBar whether should be override status bar.
    public var isFullScreen: Bool?{
    
    
        set{
    
    
            _isFullScreen = newValue ?? false
            if _isFullScreen == true {
    
    
                self.resetFrameInFullScreen()
            } else {
    
    
                self.resetFrameInUnfullScreen()
            }
        }
        get{
    
    
            return _isFullScreen
        }
    }
    
    private var _isEnableDividerLine: Bool?
    /// NavigationBar whether should be show the divider line in the bottom.
    public var isEnableDividerLine: Bool?{
    
    
        set{
    
    
            _isEnableDividerLine = newValue ?? false
            if _isEnableDividerLine == true {
    
    
                self.addSubview(bottomLine)
            } else {
    
    
                if bottomLine.superview != nil {
    
    
                    bottomLine.removeFromSuperview()
                }
            }
        }
        get{
    
    
            return _isEnableDividerLine
        }
    }

Regarding the click callback method of the return event, you can use Closure (block) to call back and throw the processing. It is more troublesome to write the closure, so here we use the trailing closure feature to write, and it can reduce the amount of code and look very simple.

Since it is written in Swift, communication with OC should also be considered. Just add before the type @objcMembers(not added here). The feature of trailing closures can also be called in OC, so there is no need to worry about event issues.

code:

The frame layout method is additionally written in an extension, which can be moved to github for details

import UIKit
import AVFAudio

/**
 SGNavigationBar to replace UINavigationBar, which is designed by inherited from UIView to show various view.
 */
class SGNavigationBar: UIView {
    
    
    
    typealias ClickAction = () -> Void
    
    // MARK: - Private constant.
    
    /// Left and right container width.
    private let CONTENT_WIDTH: CGFloat = 50
    /// Right view padding for right content.
    private let RIGHT_PADDING: CGFloat = 13
    /// Right button image name.
    private let RIGHT_IMAGE_NAME: String = "back"
    /// Left button image name.
    private let LEFT_IMAGE_NAME: String = "back"
    
    // MARK: - Set & get varibales.
    
    private var _barTintColor: UIColor = UIColor.white
    /// NavigationBar tint color.
    public var barTintColor: UIColor? {
    
    
        set{
    
    
            _barTintColor = newValue ?? UIColor.white
            self.backgroundColor = _barTintColor
        }
        get{
    
    
            return _barTintColor
        }
    }
    
    private var _barBackgroundImage: UIImage?
    /// NavigationBar background image.
    public var barBackgroundImage: UIImage?{
    
    
        set{
    
    
            _barBackgroundImage = newValue ?? UIImage(named: "")
            self.backgroundColor = UIColor(patternImage: _barBackgroundImage!)
        }
        get{
    
    
            return _barBackgroundImage
        }
    }
    
    private var _isBlur: Bool?
    /// NavigationBar whether should be presenting a blur view.
    public var isBlur: Bool? {
    
    
        set{
    
    
            _isBlur = newValue ?? false
            if _isBlur == true {
    
    
                self.effectView.isHidden = false
            } else {
    
    
                self.effectView.isHidden = true
            }
        }
        get{
    
    
            return _isBlur
        }
    }
    
    private var _isFullScreen: Bool?
    /// NavigationBar whether should be override status bar.
    public var isFullScreen: Bool?{
    
    
        set{
    
    
            _isFullScreen = newValue ?? false
            if _isFullScreen == true {
    
    
                self.resetFrameInFullScreen()
            } else {
    
    
                self.resetFrameInUnfullScreen()
            }
        }
        get{
    
    
            return _isFullScreen
        }
    }
    
    private var _isEnableDividerLine: Bool?
    /// NavigationBar whether should be show the divider line in the bottom.
    public var isEnableDividerLine: Bool?{
    
    
        set{
    
    
            _isEnableDividerLine = newValue ?? false
            if _isEnableDividerLine == true {
    
    
                self.addSubview(bottomLine)
            } else {
    
    
                if bottomLine.superview != nil {
    
    
                    bottomLine.removeFromSuperview()
                }
            }
        }
        get{
    
    
            return _isEnableDividerLine
        }
    }
    
    // MARK: - Private variables.
    
    private var leftActionClosure:   ClickAction?
    private var centerActionClosure: ClickAction?
    private var rightActionClosure:  ClickAction?

    private lazy var leftContent:      UIView = self.createLeftContent()
    private lazy var centerContent:    UIView = self.createCenterContent()
    private lazy var rightContent:     UIView = self.createRightContent()
    private lazy var bottomLine:       UIView = self.createBottomLine()
    private lazy var blurEffect: UIBlurEffect = self.createBlurEffect()
    private lazy var effectView: UIVisualEffectView = self.createEffectView()
    
    override init(frame: CGRect) {
    
    
        super.init(frame: frame)

        self.frame = CGRect(x: 0,
                            y: kSafeTopOffset(),
                            width: kScreenWidth(),
                            height: kNavigationBarHight())
        
        _ = self.effectView
        
        self.addSubview(self.leftContent)
        self.addSubview(self.centerContent)
        self.addSubview(self.rightContent)
    }

    required init?(coder: NSCoder) {
    
    
        fatalError("init(coder:) has not been implemented")
    }

}

// MARK: - Boot Convenience Init.
extension SGNavigationBar {
    
    
    
    convenience init(leftView: UIView? = nil, centerView: UIView? = nil, rightView: UIView? = nil) {
    
    
        self.init()
        
        setupView(leftView: leftView, centerView: centerView, rightView: rightView)
    }
    
    private func setupView(leftView: UIView? = nil, centerView: UIView? = nil, rightView: UIView? = nil){
    
    
        if leftView != nil {
    
    
            assert(leftView!.frame.size != .zero, "Optional leftView must be defined and can not be CGSizeZero")
            leftContent.addSubview(leftView!)
            leftView!.center = CGPoint(x: halfWidth(leftContent), y: halfHeight(leftContent))
        }
        if centerView != nil {
    
    
            assert(centerView!.frame.size != .zero, "Optional centerView must be defined and can not be CGSizeZero")
            centerContent.addSubview(centerView!)
            centerView!.center = CGPoint(x: halfWidth(self) - self.leftContent.frame.width, y: halfHeight(centerContent))
        }
        if rightView != nil {
    
    
            assert(rightView!.frame.size != .zero, "Optional rightView must be defined and can not be CGSizeZero")
            rightContent.addSubview(rightView!)
            rightView!.center = CGPoint(x: CONTENT_WIDTH - RIGHT_PADDING - halfWidth(rightView!), y: halfHeight(rightContent))
        }

    }
    
    private func createLeftContent() -> UIView{
    
    
        let view = UIView()
        view.frame = CGRect(x: 0, y: 0, width: CONTENT_WIDTH, height: kNavigationBarHight())
        return view
    }
    
    private func createCenterContent() -> UIView{
    
    
        let view = UIView()
        view.frame = CGRect(x: CONTENT_WIDTH, y: 0, width: self.bounds.width - (CONTENT_WIDTH * 2), height: kNavigationBarHight())
        return view
    }
    
    private func createRightContent() -> UIView{
    
    
        let view = UIView()
        view.frame = CGRect(x: self.centerContent.frame.maxX, y: 0, width: CONTENT_WIDTH, height: kNavigationBarHight())
        return view
    }
    
    private func createBottomLine() -> UIView{
    
    
        let view = UIView()
        view.backgroundColor = .gray.withAlphaComponent(0.3)
        view.frame = CGRect(x: 0, y: kNavigationBarHight() - 0.5, width: kScreenWidth(), height: 0.5)
        return view
    }
    
    private func createBlurEffect() -> UIBlurEffect{
    
    
        let blurEffect = UIBlurEffect(style: .light)
        return blurEffect
    }
    
    private func createEffectView() -> UIVisualEffectView{
    
    
        let view = UIVisualEffectView(effect: blurEffect)
        view.frame = CGRect(x: self.frame.origin.x,
                                y: 0,
                                width: self.frame.width,
                                height: self.frame.height)
        self.addSubview(view)
        return view
    }
    
}

// MARK: - Outside method.
extension SGNavigationBar{
    
    
    
    public func scroll(_ x: CGFloat){
    
    
        let rate = x / UIScreen.main.bounds.width
        self.alpha = rate
    }
}

// MARK: - Inside method.
extension SGNavigationBar{
    
    
    
    private func resetFrameInFullScreen(){
    
    
        self.frame = CGRect(x: self.frame.origin.x,
                            y: 0,
                            width: self.frame.width,
                            height: self.frame.height + kSafeTopOffset())
        self.effectView.frame = CGRect(x: self.effectView.frame.origin.x,
                                       y: 0,
                                       width: self.effectView.frame.width,
                                       height: self.effectView.frame.height + kSafeTopOffset())
        self.leftContent.frame = CGRect(x: 0,
                                        y: kSafeTopOffset(),
                                        width: CONTENT_WIDTH,
                                        height: kNavigationBarHight())
        self.centerContent.frame = CGRect(x: self.leftContent.frame.maxX,
                                          y: kSafeTopOffset(),
                                          width: kScreenWidth() - (CONTENT_WIDTH * 2),
                                          height: self.centerContent.frame.height)
        self.rightContent.frame = CGRect(x: self.centerContent.frame.maxX,
                                         y: kSafeTopOffset(),
                                         width: CONTENT_WIDTH,
                                         height: self.rightContent.frame.height)
        self.bottomLine.frame = CGRect(x: 0,
                                       y: kNavigationBarHight() + kStatusBarHeight() - 0.5,
                                       width: kScreenWidth(),
                                       height: 0.5)
    }
    
    private func resetFrameInUnfullScreen(){
    
    
        self.frame = CGRect(x: 0,
                            y: kSafeTopOffset(),
                            width: kScreenWidth(),
                            height: kNavigationBarHight())
        self.effectView.frame = CGRect(x: self.frame.origin.x,
                                       y: 0,
                                       width: self.frame.width,
                                       height: self.frame.height)
        self.leftContent.frame = CGRect(x: 0,
                                        y: kSafeTopOffset(),
                                        width: CONTENT_WIDTH,
                                        height: self.leftContent.frame.height)
        self.centerContent.frame = CGRect(x: CONTENT_WIDTH,
                                          y: 0,
                                          width: self.bounds.width - (CONTENT_WIDTH * 2),
                                          height: kNavigationBarHight())
        self.rightContent.frame = CGRect(x: self.centerContent.frame.maxX,
                                         y: 0,
                                         width: CONTENT_WIDTH,
                                         height: kNavigationBarHight())
        self.bottomLine.frame = CGRect(x: 0,
                                       y: kNavigationBarHight() - 0.5,
                                       width: kScreenWidth(),
                                       height: 0.5)
    }
    
}

// MARK: - Left button and center title.
extension SGNavigationBar {
    
    
    
    convenience init(title: String, leftText: String) {
    
    
        self.init()
        self.init(leftView: self.createLeftButton(text: leftText),
                  centerView: self.createCenterLabel(text: title),
                  rightView: nil)
    }
    
    private func createLeftButton(text: String) -> UIButton{
    
    
        let button = UIButton()
        button.frame.size = CGSize(width: 24, height: 24)
        if text != "" {
    
    
            button.setTitle(text, for: .normal)
        } else {
    
    
            button.setImage(UIImage(named: LEFT_IMAGE_NAME), for: .normal)
        }
        button.addTarget(self, action: #selector(leftButtonAction), for: .touchUpInside)
        return button
    }
    
    private func createCenterLabel(text: String) -> UILabel {
    
    
        let label = UILabel()
        label.textAlignment = .center
        label.textColor = .black
        label.text = text
        label.frame.size = CGSize(width: self.centerContent.bounds.width, height: kNavigationBarHight())
        return label
    }
    
    @objc private final func leftButtonAction(){
    
    
        if self.leftActionClosure != nil {
    
    
            leftActionClosure!()
        }
    }
    
    /**
     When click left area to callback this method.
     */
    public func setOnLeftClickListener(listener: ClickAction?){
    
    
        leftActionClosure = {
    
    
            if listener != nil {
    
    
                listener!()
            }
        }
    }
    
}

// MARK: - Left button and center title and right button.
// Some method use above extension content.
extension SGNavigationBar{
    
    
    
    /**
     Convenience generate a NavigationBar with three parameters.
     - Parameter title: Center text.
     - Parameter leftText: Left button title, input `""` to use image for button otherwise show the text parameter.
     - Parameter rightText: Right buttom title, input `""` to use image for button otherwise show the text parameter.
     */
    convenience init(title: String, leftText: String, rightText: String) {
    
    
        self.init()
        self.init(leftView: self.createLeftButton(text: leftText),
                  centerView: self.createCenterLabel(text: title),
                  rightView: self.createRightButton(text: rightText))
    }
    
    private func createRightButton(text: String) -> UIButton{
    
    
        let button = UIButton()
        button.frame.size = CGSize(width: 24, height: 24)
        if text != "" {
    
    
            button.setTitle(text, for: .normal)
        } else {
    
    
            button.setImage(UIImage(named: RIGHT_IMAGE_NAME), for: .normal)
        }
        button.addTarget(self, action: #selector(rightButtonAction), for: .touchUpInside)
        return button
    }

    @objc private final func rightButtonAction(){
    
    
        if self.rightActionClosure != nil {
    
    
            rightActionClosure!()
        }
    }
    
    /**
     When click right area to callback this method.
     */
    public func setOnRightClickListener(listener: ClickAction?){
    
    
        rightActionClosure = {
    
    
            if listener != nil {
    
    
                listener!()
            }
        }
    }
    
}

use

Disable UINavigationBar first, and then a few short codes will be OK (no need to set frame for NavigationBar, the layout for full screen and non-full screen is already sitting inside):

let navigationBar = SGNavigationBar(title:"OS X", leftText: "")
navigationBar.isBlur = true
navigationBar.barBackgroundImage = UIImage(named:"wall")
navigationBar.isFullScreen = true
navigationBar.setOnClickLeftListener {
    
    
// Click left .
}
self.view.addSubview(navigationBar)


renderings

insert image description here
Gaussian blur is indeed beautiful

insert image description here

Guess you like

Origin blog.csdn.net/kicinio/article/details/126072782