Stleamist
Stleamist

Reputation: 303

Customize appearance of UINavigationBar

I'm new to iOS UI design, and I'm working on writing code that displays highly customized navigation bar, which will be used my transportation app.

Here are features of my navigation bar (see figure below): Custom UINavigationBar Blueprint

  1. Layout Margins are 18, and Space between its subviews is 9.
  2. All of its subviews' height is 44, and all UIBarButtonItem is rectangle.
  3. backBarButtonItem should be positioned same as leftBarButtonItem.
  4. In normal state, its height is 80.
  5. In expanded state (such as direction fields view in map apps), its titleView will contain two UITextFields, so its height should be 136.

See Real Design Screenshots See Real Design Screenshots So I've declared subclass of UINavigationBar, and connected custom class to Navigation Bar object in storyboard using Identity Inspector. However, I'm not sure where to begin. Though I overrode sizeThatFits(_ size:) function to make its height long for now, the subviews are close to bottom. I could't find a best practice to customize UINavigationBar like Google Maps, Uber, etc.

How can I implement such navigation bar by subclassing UINavigationBar? Or are there any other solutions?

Upvotes: 3

Views: 1461

Answers (1)

Gaurav Chandarana
Gaurav Chandarana

Reputation: 754

You can subclass UINavigationBar with the following steps..

  1. Add a new class(subclass of UINavigationBar), select your ViewController's NavigationController. Select ViewController Navigation Bar from the Document Outline. Set the class to your custom class.

  2. In your custom class, do as following..

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var bound = super.sizeThatFits(size)
        bound.height += sizeToAdd //The height you want
        return bound
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setup()
    }
    
    override func layoutSubviews() {  
        super.layoutSubviews()
    
    //If you don't override this function and do as follows, you'll 
    //find that all NavigationButtons and the Title positioned at the 
    //bottom of your navigation bar.(for the Title, we still need to 
    //add one line in setup())
    
        let classes = ["UIButton", "UINavigationItemView",             
        "UINavigationButton"]
    
        for object in self.subviews{
    
            let classOfObject = String(describing: type(of: object))
    
            if classes.contains(classOfObject){
                var objectFrame = object.frame
                objectFrame.origin.y -= self.sizeToAdd
                object.frame = objectFrame
            }
        }
    
    }
    
  3. Now in setup(), add the following code to get the Title uplifted as one would want(typically!)

    func setup() -> Void {
        self.setTitleVerticalPositionAdjustment(-self.sizeToAdd, for: 
        .default)
    }
    
  4. Now, you can design a view as you want (Here, I've loaded a custom view with XIB, you can do with code too.) and add as a subview to your navigation bar, in your ViewController. For example, in your viewDidLoad..

    YOURVIEW.frame = CGRect(x: 0, y: 44, width: 
    (self.navigationController?.navigationBar.bounds.width)!, height: 
    (self.navigationController?.navigationBar.bounds.height)! + 
    self.sizeToAdd)//The height
    self.navigationController?.navigationBar.
    addSubview(YOURVIEW) 
    
  5. So, you can subclass your navigation bar this way. But when you'll push any other viewController onto this one, you'll see (if you haven't embedded another NavigationController to the viewController that you're pushing) that the view remains as it was in pushed viewController too. You can remove it from super view before pushing any other viewController. Just removing it will not give a good user experience as the other system elements(if there are any) will fade out nicely. So, do the following to give a good user experience...

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
    
        if(!(self.navigationController?.navigationBar.subviews.contains
        (YOURVIEW))!){ //Either get it working with setting a tag to 
                       //your view and checking it against all subviews 
                       //if you haven't use XIB
            self.navigationController?.navigationBar.
            addSubview(YOURVIEW)
        }else {
            UIView.transition(with: YOURVIEW, 
            duration: 0.5, options: [ANYOPTIONS], animations: {
                self.profileNavigationView.alpha = 1.0
        }) { (success) in
    
        }
    }
    

    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(true)
    
        UIView.transition(with: YOURVIEW, duration: 
        0.5, 
        options: [ANYOPTIONS], animations: {
            self.profileNavigationView.alpha = 0.0
        }) { (success) in
            //remove it here if you want
        }
    

    }

Upvotes: 2

Related Questions