skip to Main Content



I’m looking to implement a custom UITabBarController in Swift. I have searched if there is a library or something that can be used to implement exactly same design, but couldn’t find any. Most of the answers or libraries are outdated (after some changes that Apple made on NavigationBar and TabBar on iOS 15), or are not doing the exact same functionality as needed.

So, the TabBar should be with a rounded button in the center, between the button and TabBar should be some transparent space (padding). When that button is tapped it will popup a view.

The design can be seen on the image below.

enter image description here

Similar logic and UX has Binance app. So, when the button in the center is tapped, it popups a view to navigate to another View Controllers.

enter image description here

My Implementation

One of my implementations that worked best is the following one.
I have two classes CustomTabBarController and CustomTabBar.

CustomTabBarController

import UIKit

class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        setupMiddleButton()
    }

    // TabBarButton – Setup Middle Button
    func setupMiddleButton() {
    
        let middleBtn = UIButton(frame: CGRect(x: (self.view.bounds.width / 2)-25, y: -20, width: 50, height: 50))
    
        //STYLE THE BUTTON YOUR OWN WAY
        
        middleBtn.backgroundColor = .blue
        middleBtn.layer.cornerRadius = (middleBtn.layer.frame.width / 2)
    
        //add to the tabbar and add click event
        self.tabBar.addSubview(middleBtn)
        middleBtn.addTarget(self, action: #selector(self.menuButtonAction), for: .touchUpInside)
    
        self.view.layoutIfNeeded()
}

// Menu Button Touch Action
@objc func menuButtonAction(sender: UIButton) {
    self.selectedIndex = 2   //to select the middle tab. use "1" if you have only 3 tabs.
    print("MenuButton")
}
}

CustomTabBar

import UIKit

@IBDesignable
class CustomTabBar: UITabBar {
    private var shapeLayer: CALayer?
    private func addShape() {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = createPath()
        shapeLayer.strokeColor = UIColor.lightGray.cgColor
        shapeLayer.fillColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 1.0
        
        //The below 4 lines are for shadow above the bar. you can skip them if you do not want a shadow
        shapeLayer.shadowOffset = CGSize(width:0, height:0)
        shapeLayer.shadowRadius = 10
        shapeLayer.shadowColor = UIColor.gray.cgColor
        shapeLayer.shadowOpacity = 0.3
        
        if let oldShapeLayer = self.shapeLayer {
            self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
        } else {
            self.layer.insertSublayer(shapeLayer, at: 0)
        }
        self.shapeLayer = shapeLayer
    }
    override func draw(_ rect: CGRect) {
        self.addShape()
    }
    func createPath() -> CGPath {
        let height: CGFloat = 37.0
        let path = UIBezierPath()
        let centerWidth = self.frame.width / 2
        path.move(to: CGPoint(x: 0, y: 0)) // start top left
        path.addLine(to: CGPoint(x: (centerWidth - height * 2), y: 0)) // the beginning of the trough
        
        path.addCurve(to: CGPoint(x: centerWidth, y: height),
                      controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
        
        path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
                      controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
        
        path.addLine(to: CGPoint(x: self.frame.width, y: 0))
        path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
        path.addLine(to: CGPoint(x: 0, y: self.frame.height))
        path.close()
        
        return path.cgPath
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
        for member in subviews.reversed() {
            let subPoint = member.convert(point, from: self)
            guard let result = member.hitTest(subPoint, with: event) else { continue }
            return result
        }
        return nil
    }
}

Results

The results are not quite as expected.
It’s not creating a transparency on the curve in center. It’s taking background color or sometimes adding just white background. This can be noticed mostly on TableView or any other scrolling UI

enter image description here

As for popup view when Floating Button is pressed I would like to hear any suggestion from you. What would be the best option for that?

Thank you in advance for your contribution.

2

Answers


  1. What you’re seeing is the visual effect backdrop.

    If you’re still experiencing the issue, this should fix the problem…

    In your CustomTabBarController:

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        setupMiddleButton()
        
        // add these two lines
        self.tabBar.backgroundImage = UIImage()
        self.tabBar.shadowImage = UIImage()
    }
    
    Login or Signup to reply.
  2. Your are just missing couple of properties

    1. set extendedLayoutIncludesOpaqueBars to true to each of your vc

    class FirVC: UIViewController {
       override func viewDidLoad() {
            super.viewDidLoad()
            
            // set extendedLayoutIncludesOpaqueBars to true to each of your vc 
            self.extendedLayoutIncludesOpaqueBars = true
        }
        
    }
    

    2. set BG color of your custom tab bar to clear

        override init(frame: CGRect) {
            super.init(frame: frame)
    
            // set BG color to clear
            self.backgroundColor = .clear
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            
            // set BG color to clear
            self.backgroundColor = .clear
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search