以编程方式自定义 UITabBar 不规则形状

问题描述

我需要以编程方式创建自定义 tabBar 不规则形状。我发现了很多决定,但它们都与 Interface Builder 相关联。代码如下。调试时不调用自定义tabBar的所有方法

final class TabBar: UITabBarController {

    var customTabBar = CustomizedTabBar()
    override var tabBar: UITabBar {
        return customTabBar
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .naviBarBlack
        UITabBar.appearance().barTintColor = .naviBarBlack
        UITabBar.appearance().clipsToBounds = false
        tabBar.tintColor = .white
        tabBar.itemPositioning = .centered
        
        setupVCs()
    }
    
    func setupVCs() {
        guard let homeUnselected = UIImage(named: "home-unselected"),let homeSelected = UIImage(named: "home-selected"),let likeUnselected = UIImage(named: "like-unselected"),let likeSelected = UIImage(named:"like-selected") else {return}
        
        self.viewControllers = [
            
            createNavController(for: MainScreenViewController(),image: homeUnselected,selected: homeSelected),createNavController(for: UIViewController(),image: likeUnselected,selected: likeSelected)
        ]
    }
    
    private func createNavController(for rootViewController: UIViewController,image: UIImage,selected: UIImage) -> UIViewController {
        let navController = UINavigationController(rootViewController: rootViewController)
        navController.tabBarItem.image = image
        navController.tabBarItem.selectedImage = selected
        return navController
    }
}

class CustomizedTabBar: 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

        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

        // 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),controlPoint1: CGPoint(x: centerWidth + 35,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
    }

    override func point(inside point: CGPoint,with event: UIEvent?) -> Bool {
        let buttonRadius: CGFloat = 35
        return abs(self.center.x - point.x) > buttonRadius || abs(point.y) > buttonRadius
    }

    func createPathCircle() -> CGPath {

        let radius: CGFloat = 37.0
        let path = UIBezierPath()
        let centerWidth = self.frame.width / 2

        path.move(to: CGPoint(x: 0,y: 0))
        path.addLine(to: CGPoint(x: (centerWidth - radius * 2),y: 0))
        path.addArc(withCenter: CGPoint(x: centerWidth,radius: radius,startAngle: CGFloat(180).degreesToradians,endAngle: CGFloat(0).degreesToradians,clockwise: false)
        path.addLine(to: CGPoint(x: self.frame.width,y: self.frame.height))
        path.close()
        return path.cgPath
    }
}

extension CGFloat {
    var degreesToradians: CGFloat { return self * .pi / 180 }
    var radiansTodegrees: CGFloat { return self * 180 / .pi }
}

So this's what i want to see (from https://betterprogramming.pub/draw-a-custom-ios-tabbar-shape-27d298a7f4fa)

And what i get.

解决方法

虽然 UITabBarController 状态的 Apple 文档:

您永远不应该尝试操作存储在此属性中的 UITabBar 对象本身。

您可以找到许多自定义标签栏的示例。

对于您的具体方法,不要尝试覆盖 var tabBar:

相反,如果您在 Storyboard 中有 TabBarController,请将 its TabBar 的自定义类分配给 CustomizedTabBar

或者,如果您从代码中实例化控制器,您可以试试这个:

override func viewDidLoad() {
    super.viewDidLoad()

    let tabBar = { () -> CustomizedTabBar in
        let tabBar = CustomizedTabBar()
        tabBar.delegate = self
        return tabBar
    }()
    self.setValue(tabBar,forKey: "tabBar")
    
    // ... the rest of your viewDidLoad()
}

不过,我建议您通读其他几个示例,并寻找一种通用(可靠)的方法。