iosswiftuitabbaruibezierpathbezier

How to create custom curved iOS UITabBar?


So this is the navigation my designer made for our project. Height of the TabBar is 70.

curved iOS tab bar

What I have tried so far. My attempt was based on tutorial from Philipp Weiss.

https://betterprogramming.pub/draw-a-custom-ios-tabbar-shape-27d298a7f4fa

Its based on idea of creating custom IBDesignable UITabBar class and overriding draw method.

@IBDesignable
class CustomizedTabBar: UITabBar {

    private var shapeLayer: CALayer?
    
    override func draw(_ rect: CGRect) {
        self.addShape()
    }

    private func addShape() {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = createPath()
        shapeLayer.strokeColor = UIColor.blueMenu2.cgColor
        shapeLayer.fillColor = UIColor.blueMenu2.cgColor
        shapeLayer.lineWidth = 1.0

        if let oldShapeLayer = self.shapeLayer {
            self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
        } else {
            self.layer.insertSublayer(shapeLayer, at: 0)
        }

        self.shapeLayer = shapeLayer
    }


    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

        // first curve down
        path.addCurve(to: CGPoint(x: centerWidth, y: height),
                      controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
        // second curve up
        path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
                      controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))

        // complete the rect
        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
    }

I was trying to edit bezier path to reach my goal but with no success. I am not sure if this approach can work for this specific TabBar design.

Setting height of navigation to 70 was without problem.

@IBInspectable var height: CGFloat = 70

    override open func sizeThatFits(_ size: CGSize) -> CGSize {
            guard let window = UIApplication.shared.keyWindow else {
                return super.sizeThatFits(size)
            }
            var sizeThatFits = super.sizeThatFits(size)
            if #available(iOS 11.0, *) {
                sizeThatFits.height = height + window.safeAreaInsets.bottom
            } else {
                sizeThatFits.height = height
            }
            return sizeThatFits
        }

How can I create this curved TabBar?

Do u know how to make similar shape just by using bezier curves?


Solution

  • To create a UIBezierPath for your desired shape...

    enter image description here

    Here is some sample code - it's a UIView subclass, with all the path elements in layoutSubviews():

    class TabBarShapeView: UIView {
        var shapeLayer: CAShapeLayer!
        override class var layerClass: AnyClass {
            return CAShapeLayer.self
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            shapeLayer = self.layer as? CAShapeLayer
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.strokeColor = UIColor.gray.cgColor
            shapeLayer.lineWidth = 1
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            
            let middleRad: CGFloat = bounds.height - 10.0
            
            let cornerRad: CGFloat = 12.0
            
            let pth = UIBezierPath()
            
            let topLeftC: CGPoint = CGPoint(x: bounds.minX + cornerRad, y: bounds.minY + cornerRad)
            let topRightC: CGPoint = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY + cornerRad)
            let botRightC: CGPoint = CGPoint(x: bounds.maxX - cornerRad, y: bounds.maxY - cornerRad)
            let botLeftC: CGPoint = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY - cornerRad)
    
            var pt: CGPoint!
    
            // 1
            pt = CGPoint(x: bounds.minX, y: bounds.minY + cornerRad)
            pth.move(to: pt)
            
            // c1
            pth.addArc(withCenter: topLeftC, radius: cornerRad, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)
    
            // 2
            pt = CGPoint(x: bounds.midX - middleRad, y: bounds.minY)
            pth.addLine(to: pt)
    
            // c2
            pt.y += middleRad * 0.5
            pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: -.pi * 0.5, endAngle: 0.0, clockwise: true)
            
            // c3
            pt.x += middleRad * 1.0
            pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: .pi * 1.0, endAngle: 0.0, clockwise: false)
            
            // c4
            pt.x += middleRad * 1.0
            pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)
    
            // 3
            pt = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY)
            pth.addLine(to: pt)
    
            // c5
            pth.addArc(withCenter: topRightC, radius: cornerRad, startAngle: -.pi * 0.5, endAngle: 0.0, clockwise: true)
    
            // 4
            pt = CGPoint(x: bounds.maxX, y: bounds.maxY - cornerRad)
            pth.addLine(to: pt)
            
            // c6
            pth.addArc(withCenter: botRightC, radius: cornerRad, startAngle: 0.0, endAngle: .pi * 0.5, clockwise: true)
            
            // 5
            pt = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY)
            pth.addLine(to: pt)
            
            // c7
            pth.addArc(withCenter: botLeftC, radius: cornerRad, startAngle: .pi * 0.5, endAngle: .pi * 1.0, clockwise: true)
            
            pth.close()
            
            shapeLayer.path = pth.cgPath
            
        }
    }